Format code [skip actions]

This commit is contained in:
github-actions
2023-05-11 02:23:43 +00:00
parent f51fd55cb5
commit f9906c4492
730 changed files with 29212 additions and 29159 deletions

View File

@@ -1,31 +1,31 @@
package emu.grasscutter.game;
import emu.grasscutter.game.player.Player;
public class CoopRequest {
private final Player requester;
private final long requestTime;
private final long expireTime;
public CoopRequest(Player requester) {
this.requester = requester;
this.requestTime = System.currentTimeMillis();
this.expireTime = this.requestTime + 10000;
}
public Player getRequester() {
return requester;
}
public long getRequestTime() {
return requestTime;
}
public long getExpireTime() {
return expireTime;
}
public boolean isExpired() {
return System.currentTimeMillis() > getExpireTime();
}
}
package emu.grasscutter.game;
import emu.grasscutter.game.player.Player;
public class CoopRequest {
private final Player requester;
private final long requestTime;
private final long expireTime;
public CoopRequest(Player requester) {
this.requester = requester;
this.requestTime = System.currentTimeMillis();
this.expireTime = this.requestTime + 10000;
}
public Player getRequester() {
return requester;
}
public long getRequestTime() {
return requestTime;
}
public long getExpireTime() {
return expireTime;
}
public boolean isExpired() {
return System.currentTimeMillis() > getExpireTime();
}
}

View File

@@ -1,47 +1,47 @@
package emu.grasscutter.game.achievement;
import lombok.Getter;
@Getter
public class AchievementControlReturns {
private final Return ret;
private final int changedAchievementStatusNum;
private AchievementControlReturns(Return ret) {
this(ret, 0);
}
private AchievementControlReturns(Return ret, int changedAchievementStatusNum) {
this.ret = ret;
this.changedAchievementStatusNum = changedAchievementStatusNum;
}
public static AchievementControlReturns success(int changedAchievementStatusNum) {
return new AchievementControlReturns(Return.SUCCESS, changedAchievementStatusNum);
}
public static AchievementControlReturns achievementNotFound() {
return new AchievementControlReturns(Return.ACHIEVEMENT_NOT_FOUND);
}
public static AchievementControlReturns alreadyAchieved() {
return new AchievementControlReturns(Return.ALREADY_ACHIEVED);
}
public static AchievementControlReturns notYetAchieved() {
return new AchievementControlReturns(Return.NOT_YET_ACHIEVED);
}
public enum Return {
SUCCESS("commands.achievement.success."),
ACHIEVEMENT_NOT_FOUND("commands.achievement.fail.achievement_not_found"),
ALREADY_ACHIEVED("commands.achievement.fail.already_achieved"),
NOT_YET_ACHIEVED("commands.achievement.fail.not_yet_achieved");
@Getter private final String key;
Return(String key) {
this.key = key;
}
}
}
package emu.grasscutter.game.achievement;
import lombok.Getter;
@Getter
public class AchievementControlReturns {
private final Return ret;
private final int changedAchievementStatusNum;
private AchievementControlReturns(Return ret) {
this(ret, 0);
}
private AchievementControlReturns(Return ret, int changedAchievementStatusNum) {
this.ret = ret;
this.changedAchievementStatusNum = changedAchievementStatusNum;
}
public static AchievementControlReturns success(int changedAchievementStatusNum) {
return new AchievementControlReturns(Return.SUCCESS, changedAchievementStatusNum);
}
public static AchievementControlReturns achievementNotFound() {
return new AchievementControlReturns(Return.ACHIEVEMENT_NOT_FOUND);
}
public static AchievementControlReturns alreadyAchieved() {
return new AchievementControlReturns(Return.ALREADY_ACHIEVED);
}
public static AchievementControlReturns notYetAchieved() {
return new AchievementControlReturns(Return.NOT_YET_ACHIEVED);
}
public enum Return {
SUCCESS("commands.achievement.success."),
ACHIEVEMENT_NOT_FOUND("commands.achievement.fail.achievement_not_found"),
ALREADY_ACHIEVED("commands.achievement.fail.already_achieved"),
NOT_YET_ACHIEVED("commands.achievement.fail.not_yet_achieved");
@Getter private final String key;
Return(String key) {
this.key = key;
}
}
}

View File

@@ -1,13 +1,13 @@
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.WatcherTriggerType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ActivityWatcherType {
WatcherTriggerType value();
}
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.WatcherTriggerType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ActivityWatcherType {
WatcherTriggerType value();
}

View File

@@ -1,15 +1,15 @@
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
@GameActivity(ActivityType.NONE)
public class DefaultActivityHandler extends ActivityHandler {
@Override
public void onProtoBuild(
PlayerActivityData playerActivityData,
ActivityInfoOuterClass.ActivityInfo.Builder activityInfo) {}
@Override
public void onInitPlayerActivityData(PlayerActivityData playerActivityData) {}
}
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
@GameActivity(ActivityType.NONE)
public class DefaultActivityHandler extends ActivityHandler {
@Override
public void onProtoBuild(
PlayerActivityData playerActivityData,
ActivityInfoOuterClass.ActivityInfo.Builder activityInfo) {}
@Override
public void onInitPlayerActivityData(PlayerActivityData playerActivityData) {}
}

View File

@@ -1,11 +1,11 @@
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.WatcherTriggerType;
@ActivityWatcherType(WatcherTriggerType.TRIGGER_NONE)
public class DefaultWatcher extends ActivityWatcher {
@Override
protected boolean isMeet(String... param) {
return false;
}
}
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.WatcherTriggerType;
@ActivityWatcherType(WatcherTriggerType.TRIGGER_NONE)
public class DefaultWatcher extends ActivityWatcher {
@Override
protected boolean isMeet(String... param) {
return false;
}
}

View File

@@ -1,13 +1,13 @@
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.ActivityType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface GameActivity {
ActivityType value();
}
package emu.grasscutter.game.activity;
import emu.grasscutter.game.props.ActivityType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface GameActivity {
ActivityType value();
}

View File

@@ -1,105 +1,105 @@
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.game.activity.ActivityHandler;
import emu.grasscutter.game.activity.GameActivity;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
import emu.grasscutter.net.proto.MusicGameActivityDetailInfoOuterClass;
import emu.grasscutter.utils.JsonUtils;
import java.util.stream.Collectors;
@GameActivity(ActivityType.NEW_ACTIVITY_MUSIC_GAME)
public class MusicGameActivityHandler extends ActivityHandler {
@Override
public void onInitPlayerActivityData(PlayerActivityData playerActivityData) {
var musicGamePlayerData = MusicGamePlayerData.create();
playerActivityData.setDetail(musicGamePlayerData);
}
@Override
public void onProtoBuild(
PlayerActivityData playerActivityData,
ActivityInfoOuterClass.ActivityInfo.Builder activityInfo) {
MusicGamePlayerData musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
activityInfo.setMusicGameInfo(
MusicGameActivityDetailInfoOuterClass.MusicGameActivityDetailInfo.newBuilder()
.putAllMusicGameRecordMap(
musicGamePlayerData.getMusicGameRecord().values().stream()
.collect(
Collectors.toMap(
MusicGamePlayerData.MusicGameRecord::getMusicId,
MusicGamePlayerData.MusicGameRecord::toProto)))
//
// .addAllPersonCustomBeatmap(musicGamePlayerData.getPersonalCustomBeatmapRecord().values().stream()
// .map(MusicGamePlayerData.CustomBeatmapRecord::toPersonalBriefProto)
// .map(UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder::build)
// .toList())
//
//
// .addAllOthersCustomBeatmap(musicGamePlayerData.getOthersCustomBeatmapRecord().values().stream()
// .map(MusicGamePlayerData.CustomBeatmapRecord::toOthersBriefProto)
// .map(UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder::build)
// .toList())
.build());
}
public MusicGamePlayerData getMusicGamePlayerData(PlayerActivityData playerActivityData) {
if (playerActivityData.getDetail() == null || playerActivityData.getDetail().isBlank()) {
onInitPlayerActivityData(playerActivityData);
playerActivityData.save();
}
return JsonUtils.decode(playerActivityData.getDetail(), MusicGamePlayerData.class);
}
public boolean setMusicGameRecord(
PlayerActivityData playerActivityData, MusicGamePlayerData.MusicGameRecord newRecord) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
var saveRecord = musicGamePlayerData.getMusicGameRecord().get(newRecord.getMusicId());
saveRecord.setMaxCombo(Math.max(newRecord.getMaxCombo(), saveRecord.getMaxCombo()));
saveRecord.setMaxScore(Math.max(newRecord.getMaxScore(), saveRecord.getMaxScore()));
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
return newRecord.getMaxScore() > saveRecord.getMaxScore();
}
public void setMusicGameCustomBeatmapRecord(
PlayerActivityData playerActivityData, MusicGamePlayerData.CustomBeatmapRecord newRecord) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData.getOthersCustomBeatmapRecord().put(newRecord.getMusicShareId(), newRecord);
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
public void addPersonalBeatmap(
PlayerActivityData playerActivityData, MusicGameBeatmap musicGameBeatmap) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData
.getPersonalCustomBeatmapRecord()
.put(
musicGameBeatmap.getMusicShareId(),
MusicGamePlayerData.CustomBeatmapRecord.of()
.musicShareId(musicGameBeatmap.getMusicShareId())
.build());
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
public void removePersonalBeatmap(
PlayerActivityData playerActivityData, MusicGameBeatmap musicGameBeatmap) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData.getPersonalCustomBeatmapRecord().remove(musicGameBeatmap.getMusicShareId());
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
}
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.game.activity.ActivityHandler;
import emu.grasscutter.game.activity.GameActivity;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
import emu.grasscutter.net.proto.MusicGameActivityDetailInfoOuterClass;
import emu.grasscutter.utils.JsonUtils;
import java.util.stream.Collectors;
@GameActivity(ActivityType.NEW_ACTIVITY_MUSIC_GAME)
public class MusicGameActivityHandler extends ActivityHandler {
@Override
public void onInitPlayerActivityData(PlayerActivityData playerActivityData) {
var musicGamePlayerData = MusicGamePlayerData.create();
playerActivityData.setDetail(musicGamePlayerData);
}
@Override
public void onProtoBuild(
PlayerActivityData playerActivityData,
ActivityInfoOuterClass.ActivityInfo.Builder activityInfo) {
MusicGamePlayerData musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
activityInfo.setMusicGameInfo(
MusicGameActivityDetailInfoOuterClass.MusicGameActivityDetailInfo.newBuilder()
.putAllMusicGameRecordMap(
musicGamePlayerData.getMusicGameRecord().values().stream()
.collect(
Collectors.toMap(
MusicGamePlayerData.MusicGameRecord::getMusicId,
MusicGamePlayerData.MusicGameRecord::toProto)))
//
// .addAllPersonCustomBeatmap(musicGamePlayerData.getPersonalCustomBeatmapRecord().values().stream()
// .map(MusicGamePlayerData.CustomBeatmapRecord::toPersonalBriefProto)
// .map(UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder::build)
// .toList())
//
//
// .addAllOthersCustomBeatmap(musicGamePlayerData.getOthersCustomBeatmapRecord().values().stream()
// .map(MusicGamePlayerData.CustomBeatmapRecord::toOthersBriefProto)
// .map(UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder::build)
// .toList())
.build());
}
public MusicGamePlayerData getMusicGamePlayerData(PlayerActivityData playerActivityData) {
if (playerActivityData.getDetail() == null || playerActivityData.getDetail().isBlank()) {
onInitPlayerActivityData(playerActivityData);
playerActivityData.save();
}
return JsonUtils.decode(playerActivityData.getDetail(), MusicGamePlayerData.class);
}
public boolean setMusicGameRecord(
PlayerActivityData playerActivityData, MusicGamePlayerData.MusicGameRecord newRecord) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
var saveRecord = musicGamePlayerData.getMusicGameRecord().get(newRecord.getMusicId());
saveRecord.setMaxCombo(Math.max(newRecord.getMaxCombo(), saveRecord.getMaxCombo()));
saveRecord.setMaxScore(Math.max(newRecord.getMaxScore(), saveRecord.getMaxScore()));
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
return newRecord.getMaxScore() > saveRecord.getMaxScore();
}
public void setMusicGameCustomBeatmapRecord(
PlayerActivityData playerActivityData, MusicGamePlayerData.CustomBeatmapRecord newRecord) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData.getOthersCustomBeatmapRecord().put(newRecord.getMusicShareId(), newRecord);
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
public void addPersonalBeatmap(
PlayerActivityData playerActivityData, MusicGameBeatmap musicGameBeatmap) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData
.getPersonalCustomBeatmapRecord()
.put(
musicGameBeatmap.getMusicShareId(),
MusicGamePlayerData.CustomBeatmapRecord.of()
.musicShareId(musicGameBeatmap.getMusicShareId())
.build());
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
public void removePersonalBeatmap(
PlayerActivityData playerActivityData, MusicGameBeatmap musicGameBeatmap) {
var musicGamePlayerData = getMusicGamePlayerData(playerActivityData);
musicGamePlayerData.getPersonalCustomBeatmapRecord().remove(musicGameBeatmap.getMusicShareId());
playerActivityData.setDetail(musicGamePlayerData);
playerActivityData.save();
}
}

View File

@@ -1,97 +1,97 @@
package emu.grasscutter.game.activity.musicgame;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.net.proto.UgcMusicBriefInfoOuterClass;
import emu.grasscutter.net.proto.UgcMusicNoteOuterClass;
import emu.grasscutter.net.proto.UgcMusicRecordOuterClass;
import emu.grasscutter.net.proto.UgcMusicTrackOuterClass;
import java.util.List;
import java.util.Random;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity("music_game_beatmaps")
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class MusicGameBeatmap {
@Id long musicShareId;
int authorUid;
int musicId;
int musicNoteCount;
int savePosition;
int maxScore;
int createTime;
List<List<BeatmapNote>> beatmap;
public static MusicGameBeatmap getByShareId(long musicShareId) {
return DatabaseHelper.getMusicGameBeatmap(musicShareId);
}
public static List<List<BeatmapNote>> parse(
List<UgcMusicTrackOuterClass.UgcMusicTrack> beatmapItemListList) {
return beatmapItemListList.stream()
.map(item -> item.getMusicNoteListList().stream().map(BeatmapNote::parse).toList())
.toList();
}
public void save() {
if (musicShareId == 0) {
musicShareId = new Random().nextLong(100000000000000L, 999999999999999L);
}
DatabaseHelper.saveMusicGameBeatmap(this);
}
public UgcMusicRecordOuterClass.UgcMusicRecord toProto() {
return UgcMusicRecordOuterClass.UgcMusicRecord.newBuilder()
.setMusicId(musicId)
.addAllMusicTrackList(beatmap.stream().map(this::musicBeatmapListToProto).toList())
.build();
}
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toBriefProto() {
var player = DatabaseHelper.getPlayerByUid(authorUid);
return UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.newBuilder()
.setMusicId(musicId)
// .setMusicNoteCount(musicNoteCount)
.setUgcGuid(musicShareId)
.setMaxScore(maxScore)
// .setShareTime(createTime)
.setCreatorNickname(player.getNickname())
.setVersion(1);
}
private UgcMusicTrackOuterClass.UgcMusicTrack musicBeatmapListToProto(
List<BeatmapNote> beatmapNoteList) {
return UgcMusicTrackOuterClass.UgcMusicTrack.newBuilder()
.addAllMusicNoteList(beatmapNoteList.stream().map(BeatmapNote::toProto).toList())
.build();
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
@Entity
public static class BeatmapNote {
int startTime;
int endTime;
public static BeatmapNote parse(UgcMusicNoteOuterClass.UgcMusicNote note) {
return BeatmapNote.of().startTime(note.getStartTime()).endTime(note.getEndTime()).build();
}
public UgcMusicNoteOuterClass.UgcMusicNote toProto() {
return UgcMusicNoteOuterClass.UgcMusicNote.newBuilder()
.setStartTime(startTime)
.setEndTime(endTime)
.build();
}
}
}
package emu.grasscutter.game.activity.musicgame;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.net.proto.UgcMusicBriefInfoOuterClass;
import emu.grasscutter.net.proto.UgcMusicNoteOuterClass;
import emu.grasscutter.net.proto.UgcMusicRecordOuterClass;
import emu.grasscutter.net.proto.UgcMusicTrackOuterClass;
import java.util.List;
import java.util.Random;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity("music_game_beatmaps")
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class MusicGameBeatmap {
@Id long musicShareId;
int authorUid;
int musicId;
int musicNoteCount;
int savePosition;
int maxScore;
int createTime;
List<List<BeatmapNote>> beatmap;
public static MusicGameBeatmap getByShareId(long musicShareId) {
return DatabaseHelper.getMusicGameBeatmap(musicShareId);
}
public static List<List<BeatmapNote>> parse(
List<UgcMusicTrackOuterClass.UgcMusicTrack> beatmapItemListList) {
return beatmapItemListList.stream()
.map(item -> item.getMusicNoteListList().stream().map(BeatmapNote::parse).toList())
.toList();
}
public void save() {
if (musicShareId == 0) {
musicShareId = new Random().nextLong(100000000000000L, 999999999999999L);
}
DatabaseHelper.saveMusicGameBeatmap(this);
}
public UgcMusicRecordOuterClass.UgcMusicRecord toProto() {
return UgcMusicRecordOuterClass.UgcMusicRecord.newBuilder()
.setMusicId(musicId)
.addAllMusicTrackList(beatmap.stream().map(this::musicBeatmapListToProto).toList())
.build();
}
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toBriefProto() {
var player = DatabaseHelper.getPlayerByUid(authorUid);
return UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.newBuilder()
.setMusicId(musicId)
// .setMusicNoteCount(musicNoteCount)
.setUgcGuid(musicShareId)
.setMaxScore(maxScore)
// .setShareTime(createTime)
.setCreatorNickname(player.getNickname())
.setVersion(1);
}
private UgcMusicTrackOuterClass.UgcMusicTrack musicBeatmapListToProto(
List<BeatmapNote> beatmapNoteList) {
return UgcMusicTrackOuterClass.UgcMusicTrack.newBuilder()
.addAllMusicNoteList(beatmapNoteList.stream().map(BeatmapNote::toProto).toList())
.build();
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
@Entity
public static class BeatmapNote {
int startTime;
int endTime;
public static BeatmapNote parse(UgcMusicNoteOuterClass.UgcMusicNote note) {
return BeatmapNote.of().startTime(note.getStartTime()).endTime(note.getEndTime()).build();
}
public UgcMusicNoteOuterClass.UgcMusicNote toProto() {
return UgcMusicNoteOuterClass.UgcMusicNote.newBuilder()
.setStartTime(startTime)
.setEndTime(endTime)
.build();
}
}
}

View File

@@ -1,86 +1,86 @@
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.MusicGameBasicData;
import emu.grasscutter.net.proto.MusicGameRecordOuterClass;
import emu.grasscutter.net.proto.UgcMusicBriefInfoOuterClass;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class MusicGamePlayerData {
Map<Integer, MusicGameRecord> musicGameRecord;
Map<Long, CustomBeatmapRecord> personalCustomBeatmapRecord;
Map<Long, CustomBeatmapRecord> othersCustomBeatmapRecord;
public static MusicGamePlayerData create() {
return MusicGamePlayerData.of()
.musicGameRecord(
GameData.getMusicGameBasicDataMap().values().stream()
.collect(
Collectors.toMap(
MusicGameBasicData::getId, MusicGamePlayerData.MusicGameRecord::create)))
.personalCustomBeatmapRecord(new HashMap<>())
.othersCustomBeatmapRecord(new HashMap<>())
.build();
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class MusicGameRecord {
int musicId;
int maxCombo;
int maxScore;
public static MusicGameRecord create(MusicGameBasicData musicGameBasicData) {
return MusicGameRecord.of().musicId(musicGameBasicData.getId()).build();
}
public MusicGameRecordOuterClass.MusicGameRecord toProto() {
return MusicGameRecordOuterClass.MusicGameRecord.newBuilder()
.setIsUnlock(true)
.setMaxCombo(maxCombo)
.setMaxScore(maxScore)
.build();
}
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class CustomBeatmapRecord {
long musicShareId;
int score;
boolean settle;
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toPersonalBriefProto() {
var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId);
return UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.newBuilder()
// .setCanShare(true)
// .setCreateTime(musicGameBeatmap.getCreateTime())
.setMusicId(musicGameBeatmap.getMusicId())
.setMaxScore(musicGameBeatmap.getMaxScore())
// .setPosition(musicGameBeatmap.getSavePosition())
// .setMusicNoteCount(musicGameBeatmap.getMusicNoteCount())
.setUgcGuid(musicShareId);
}
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toOthersBriefProto() {
var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId);
return musicGameBeatmap.toBriefProto()
// .setScore(score)
// .setSettle(settle)
;
}
}
}
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.MusicGameBasicData;
import emu.grasscutter.net.proto.MusicGameRecordOuterClass;
import emu.grasscutter.net.proto.UgcMusicBriefInfoOuterClass;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class MusicGamePlayerData {
Map<Integer, MusicGameRecord> musicGameRecord;
Map<Long, CustomBeatmapRecord> personalCustomBeatmapRecord;
Map<Long, CustomBeatmapRecord> othersCustomBeatmapRecord;
public static MusicGamePlayerData create() {
return MusicGamePlayerData.of()
.musicGameRecord(
GameData.getMusicGameBasicDataMap().values().stream()
.collect(
Collectors.toMap(
MusicGameBasicData::getId, MusicGamePlayerData.MusicGameRecord::create)))
.personalCustomBeatmapRecord(new HashMap<>())
.othersCustomBeatmapRecord(new HashMap<>())
.build();
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class MusicGameRecord {
int musicId;
int maxCombo;
int maxScore;
public static MusicGameRecord create(MusicGameBasicData musicGameBasicData) {
return MusicGameRecord.of().musicId(musicGameBasicData.getId()).build();
}
public MusicGameRecordOuterClass.MusicGameRecord toProto() {
return MusicGameRecordOuterClass.MusicGameRecord.newBuilder()
.setIsUnlock(true)
.setMaxCombo(maxCombo)
.setMaxScore(maxScore)
.build();
}
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class CustomBeatmapRecord {
long musicShareId;
int score;
boolean settle;
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toPersonalBriefProto() {
var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId);
return UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.newBuilder()
// .setCanShare(true)
// .setCreateTime(musicGameBeatmap.getCreateTime())
.setMusicId(musicGameBeatmap.getMusicId())
.setMaxScore(musicGameBeatmap.getMaxScore())
// .setPosition(musicGameBeatmap.getSavePosition())
// .setMusicNoteCount(musicGameBeatmap.getMusicNoteCount())
.setUgcGuid(musicShareId);
}
public UgcMusicBriefInfoOuterClass.UgcMusicBriefInfo.Builder toOthersBriefProto() {
var musicGameBeatmap = MusicGameBeatmap.getByShareId(musicShareId);
return musicGameBeatmap.toBriefProto()
// .setScore(score)
// .setSettle(settle)
;
}
}
}

View File

@@ -1,23 +1,23 @@
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.game.activity.ActivityWatcher;
import emu.grasscutter.game.activity.ActivityWatcherType;
import emu.grasscutter.game.props.WatcherTriggerType;
@ActivityWatcherType(WatcherTriggerType.TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE)
public class MusicGameScoreTrigger extends ActivityWatcher {
@Override
protected boolean isMeet(String... param) {
if (param.length != 2) {
return false;
}
var paramList = getActivityWatcherData().getTriggerConfig().getParamList();
if (!paramList.get(0).equals(param[0])) {
return false;
}
var score = Integer.parseInt(param[1]);
var target = Integer.parseInt(paramList.get(1));
return score >= target;
}
}
package emu.grasscutter.game.activity.musicgame;
import emu.grasscutter.game.activity.ActivityWatcher;
import emu.grasscutter.game.activity.ActivityWatcherType;
import emu.grasscutter.game.props.WatcherTriggerType;
@ActivityWatcherType(WatcherTriggerType.TRIGGER_FLEUR_FAIR_MUSIC_GAME_REACH_SCORE)
public class MusicGameScoreTrigger extends ActivityWatcher {
@Override
protected boolean isMeet(String... param) {
if (param.length != 2) {
return false;
}
var paramList = getActivityWatcherData().getTriggerConfig().getParamList();
if (!paramList.get(0).equals(param[0])) {
return false;
}
var score = Integer.parseInt(param[1]);
var target = Integer.parseInt(paramList.get(1));
return score >= target;
}
}

View File

@@ -1,415 +1,415 @@
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.BattlePassRewardData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.RewardData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.game.player.BasePlayerDataManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.BattlePassMissionRefreshType;
import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.BattlePassCycleOuterClass.BattlePassCycle;
import emu.grasscutter.net.proto.BattlePassRewardTakeOptionOuterClass.BattlePassRewardTakeOption;
import emu.grasscutter.net.proto.BattlePassScheduleOuterClass.BattlePassSchedule;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
import emu.grasscutter.server.packet.send.PacketBattlePassCurScheduleUpdateNotify;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import emu.grasscutter.server.packet.send.PacketTakeBattlePassRewardRsp;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import org.bson.types.ObjectId;
@Entity(value = "battlepass", useDiscriminator = false)
public class BattlePassManager extends BasePlayerDataManager {
@Id @Getter private ObjectId id;
@Indexed private int ownerUid;
@Getter private int point;
@Getter private int cyclePoints; // Weekly maximum cap
@Getter private int level;
@Getter private boolean viewed;
private boolean paid;
private Map<Integer, BattlePassMission> missions;
private Map<Integer, BattlePassReward> takenRewards;
@Deprecated // Morphia only
public BattlePassManager() {}
public BattlePassManager(Player player) {
super(player);
this.ownerUid = player.getUid();
}
public void setPlayer(Player player) {
this.player = player;
this.ownerUid = player.getUid();
}
public void updateViewed() {
this.viewed = true;
}
public boolean setLevel(int level) {
if (level >= 0 && level <= GameConstants.BATTLE_PASS_MAX_LEVEL) {
this.level = level;
this.point = 0;
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.player));
return true;
}
return false;
}
public void addPoints(int points) {
this.addPointsDirectly(points, false);
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(player));
this.save();
}
public void addPointsDirectly(int points, boolean isWeekly) {
int amount = points;
if (isWeekly) {
amount = Math.min(amount, GameConstants.BATTLE_PASS_POINT_PER_WEEK - this.cyclePoints);
}
if (amount <= 0) {
return;
}
this.point += amount;
this.cyclePoints += amount;
if (this.point >= GameConstants.BATTLE_PASS_POINT_PER_LEVEL
&& this.getLevel() < GameConstants.BATTLE_PASS_MAX_LEVEL) {
int levelups = Math.floorDiv(this.point, GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
// Make sure player cant go above max BP level
levelups = Math.min(levelups, GameConstants.BATTLE_PASS_MAX_LEVEL - levelups);
// Set new points after level up
this.point = this.point - (levelups * GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
this.level += levelups;
}
}
public Map<Integer, BattlePassMission> getMissions() {
if (this.missions == null) this.missions = new HashMap<>();
return this.missions;
}
// Will return a new empty mission if the mission id is not found
public BattlePassMission loadMissionById(int id) {
return getMissions().computeIfAbsent(id, i -> new BattlePassMission(i));
}
public boolean hasMission(int id) {
return getMissions().containsKey(id);
}
public boolean isPaid() {
// ToDo: Change this when we actually support unlocking "paid" BP.
return true;
}
public Map<Integer, BattlePassReward> getTakenRewards() {
if (this.takenRewards == null) this.takenRewards = new HashMap<>();
return this.takenRewards;
}
// Mission triggers
public void triggerMission(WatcherTriggerType triggerType) {
getPlayer().getServer().getBattlePassSystem().triggerMission(getPlayer(), triggerType);
}
public void triggerMission(WatcherTriggerType triggerType, int param, int progress) {
getPlayer()
.getServer()
.getBattlePassSystem()
.triggerMission(getPlayer(), triggerType, param, progress);
}
// Handlers
public void takeMissionPoint(List<Integer> missionIdList) {
// Obvious exploit check
if (missionIdList.size() > GameData.getBattlePassMissionDataMap().size()) {
return;
}
List<BattlePassMission> updatedMissions = new ArrayList<>(missionIdList.size());
for (int id : missionIdList) {
// Skip if we dont have this mission
if (!this.hasMission(id)) {
continue;
}
BattlePassMission mission = this.loadMissionById(id);
if (mission.getData() == null) {
this.getMissions().remove(mission.getId());
continue;
}
// Take reward
if (mission.getStatus() == BattlePassMissionStatus.MISSION_STATUS_FINISHED) {
this.addPointsDirectly(mission.getData().getAddPoint(), mission.getData().isCycleRefresh());
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_POINT_TAKEN);
updatedMissions.add(mission);
}
}
if (updatedMissions.size() > 0) {
// Save to db
this.save();
// Packet
getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(updatedMissions));
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
}
private void takeRewardsFromSelectChest(
ItemData rewardItemData, int index, ItemParamData entry, List<GameItem> rewardItems) {
// Sanity checks.
if (rewardItemData.getItemUse().size() < 1) {
return;
}
// Get possible item choices.
String[] choices = rewardItemData.getItemUse().get(0).getUseParam()[0].split(",");
if (choices.length < index) {
return;
}
// Get data for the selected item.
// This depends on the type of chest.
int chosenId = Integer.parseInt(choices[index - 1]);
// For ITEM_USE_ADD_SELECT_ITEM chests, we can directly add the item specified in the chest's
// data.
if (rewardItemData.getItemUse().get(0).getUseOp() == ItemUseOp.ITEM_USE_ADD_SELECT_ITEM) {
GameItem rewardItem =
new GameItem(GameData.getItemDataMap().get(chosenId), entry.getItemCount());
rewardItems.add(rewardItem);
}
// For ITEM_USE_GRANT_SELECT_REWARD chests, we have to again look up reward data.
else if (rewardItemData.getItemUse().get(0).getUseOp()
== ItemUseOp.ITEM_USE_GRANT_SELECT_REWARD) {
RewardData selectedReward = GameData.getRewardDataMap().get(chosenId);
for (var r : selectedReward.getRewardItemList()) {
GameItem rewardItem =
new GameItem(GameData.getItemDataMap().get(r.getItemId()), r.getItemCount());
rewardItems.add(rewardItem);
}
} else {
Grasscutter.getLogger().error("Invalid chest type for BP reward.");
}
}
public void takeReward(List<BattlePassRewardTakeOption> takeOptionList) {
List<BattlePassRewardTakeOption> rewardList = new ArrayList<>();
for (BattlePassRewardTakeOption option : takeOptionList) {
// Duplicate check
if (option.getTag().getRewardId() == 0
|| getTakenRewards().containsKey(option.getTag().getRewardId())) {
continue;
}
// Level check
if (option.getTag().getLevel() > this.getLevel()) {
continue;
}
BattlePassRewardData rewardData =
GameData.getBattlePassRewardDataMap()
.get(GameConstants.BATTLE_PASS_CURRENT_INDEX * 100 + option.getTag().getLevel());
// Sanity check with excel data
if (rewardData.getFreeRewardIdList().contains(option.getTag().getRewardId())) {
rewardList.add(option);
} else if (this.isPaid()
&& rewardData.getPaidRewardIdList().contains(option.getTag().getRewardId())) {
rewardList.add(option);
} else {
Grasscutter.getLogger().info("Not in rewards list: {}", option.getTag().getRewardId());
}
}
// Get rewards
List<GameItem> rewardItems = null;
if (rewardList.size() > 0) {
rewardItems = new ArrayList<>();
for (var option : rewardList) {
var tag = option.getTag();
int index = option.getOptionIdx();
// Make sure we have reward data.
RewardData reward = GameData.getRewardDataMap().get(tag.getRewardId());
if (reward == null) {
continue;
}
// Add reward items.
for (var entry : reward.getRewardItemList()) {
ItemData rewardItemData = GameData.getItemDataMap().get(entry.getItemId());
// Some rewards are chests where the user can select the item they want.
if (rewardItemData.getMaterialType() == MaterialType.MATERIAL_SELECTABLE_CHEST) {
this.takeRewardsFromSelectChest(rewardItemData, index, entry, rewardItems);
}
// All other rewards directly give us the right item.
else {
GameItem rewardItem = new GameItem(rewardItemData, entry.getItemCount());
rewardItems.add(rewardItem);
}
}
// Construct the reward and set as taken.
BattlePassReward bpReward =
new BattlePassReward(
tag.getLevel(),
tag.getRewardId(),
tag.getUnlockStatus() == BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID);
this.getTakenRewards().put(bpReward.getRewardId(), bpReward);
}
// Save to db
this.save();
// Add items and send battle pass schedule packet
getPlayer().getInventory().addItems(rewardItems);
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
getPlayer().sendPacket(new PacketTakeBattlePassRewardRsp(takeOptionList, rewardItems));
}
public int buyLevels(int buyLevel) {
int boughtLevels = Math.min(buyLevel, GameConstants.BATTLE_PASS_MAX_LEVEL - buyLevel);
if (boughtLevels > 0) {
int price = GameConstants.BATTLE_PASS_LEVEL_PRICE * boughtLevels;
if (getPlayer().getPrimogems() < price) {
return 0;
}
this.level += boughtLevels;
this.save();
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
return boughtLevels;
}
public void resetDailyMissions() {
var resetMissions = new ArrayList<BattlePassMission>();
for (var mission : this.missions.values()) {
if (mission.getData().getRefreshType() == null
|| mission.getData().getRefreshType()
== BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_DAILY) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_UNFINISHED);
mission.setProgress(0);
resetMissions.add(mission);
}
}
this.getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(resetMissions));
this.getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.getPlayer()));
}
public void resetWeeklyMissions() {
var resetMissions = new ArrayList<BattlePassMission>();
for (var mission : this.missions.values()) {
if (mission.getData().getRefreshType()
== BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_UNFINISHED);
mission.setProgress(0);
resetMissions.add(mission);
}
}
this.getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(resetMissions));
this.getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.getPlayer()));
}
//
public BattlePassSchedule getScheduleProto() {
var currentDate = LocalDate.now();
var nextSundayDate =
(currentDate.getDayOfWeek() == DayOfWeek.SUNDAY)
? currentDate
: LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
var nextSundayTime =
LocalDateTime.of(
nextSundayDate.getYear(),
nextSundayDate.getMonthValue(),
nextSundayDate.getDayOfMonth(),
23,
59,
59);
BattlePassSchedule.Builder schedule =
BattlePassSchedule.newBuilder()
.setScheduleId(2700)
.setLevel(this.getLevel())
.setPoint(this.getPoint())
.setBeginTime(0)
.setEndTime(2059483200)
.setIsViewed(this.isViewed())
.setUnlockStatus(
this.isPaid()
? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID
: BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE)
.setPaidPlatformFlags(2) // Not bought on Playstation.
.setCurCyclePoints(this.getCyclePoints())
.setCurCycle(
BattlePassCycle.newBuilder()
.setBeginTime(0)
.setEndTime((int) nextSundayTime.atZone(ZoneId.systemDefault()).toEpochSecond())
.setCycleIdx(3));
for (BattlePassReward reward : getTakenRewards().values()) {
schedule.addRewardTakenList(reward.toProto());
}
return schedule.build();
}
public void save() {
DatabaseHelper.saveBattlePass(this);
}
}
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.BattlePassRewardData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.RewardData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.game.player.BasePlayerDataManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.BattlePassMissionRefreshType;
import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.BattlePassCycleOuterClass.BattlePassCycle;
import emu.grasscutter.net.proto.BattlePassRewardTakeOptionOuterClass.BattlePassRewardTakeOption;
import emu.grasscutter.net.proto.BattlePassScheduleOuterClass.BattlePassSchedule;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
import emu.grasscutter.server.packet.send.PacketBattlePassCurScheduleUpdateNotify;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import emu.grasscutter.server.packet.send.PacketTakeBattlePassRewardRsp;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import org.bson.types.ObjectId;
@Entity(value = "battlepass", useDiscriminator = false)
public class BattlePassManager extends BasePlayerDataManager {
@Id @Getter private ObjectId id;
@Indexed private int ownerUid;
@Getter private int point;
@Getter private int cyclePoints; // Weekly maximum cap
@Getter private int level;
@Getter private boolean viewed;
private boolean paid;
private Map<Integer, BattlePassMission> missions;
private Map<Integer, BattlePassReward> takenRewards;
@Deprecated // Morphia only
public BattlePassManager() {}
public BattlePassManager(Player player) {
super(player);
this.ownerUid = player.getUid();
}
public void setPlayer(Player player) {
this.player = player;
this.ownerUid = player.getUid();
}
public void updateViewed() {
this.viewed = true;
}
public boolean setLevel(int level) {
if (level >= 0 && level <= GameConstants.BATTLE_PASS_MAX_LEVEL) {
this.level = level;
this.point = 0;
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.player));
return true;
}
return false;
}
public void addPoints(int points) {
this.addPointsDirectly(points, false);
this.player.sendPacket(new PacketBattlePassCurScheduleUpdateNotify(player));
this.save();
}
public void addPointsDirectly(int points, boolean isWeekly) {
int amount = points;
if (isWeekly) {
amount = Math.min(amount, GameConstants.BATTLE_PASS_POINT_PER_WEEK - this.cyclePoints);
}
if (amount <= 0) {
return;
}
this.point += amount;
this.cyclePoints += amount;
if (this.point >= GameConstants.BATTLE_PASS_POINT_PER_LEVEL
&& this.getLevel() < GameConstants.BATTLE_PASS_MAX_LEVEL) {
int levelups = Math.floorDiv(this.point, GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
// Make sure player cant go above max BP level
levelups = Math.min(levelups, GameConstants.BATTLE_PASS_MAX_LEVEL - levelups);
// Set new points after level up
this.point = this.point - (levelups * GameConstants.BATTLE_PASS_POINT_PER_LEVEL);
this.level += levelups;
}
}
public Map<Integer, BattlePassMission> getMissions() {
if (this.missions == null) this.missions = new HashMap<>();
return this.missions;
}
// Will return a new empty mission if the mission id is not found
public BattlePassMission loadMissionById(int id) {
return getMissions().computeIfAbsent(id, i -> new BattlePassMission(i));
}
public boolean hasMission(int id) {
return getMissions().containsKey(id);
}
public boolean isPaid() {
// ToDo: Change this when we actually support unlocking "paid" BP.
return true;
}
public Map<Integer, BattlePassReward> getTakenRewards() {
if (this.takenRewards == null) this.takenRewards = new HashMap<>();
return this.takenRewards;
}
// Mission triggers
public void triggerMission(WatcherTriggerType triggerType) {
getPlayer().getServer().getBattlePassSystem().triggerMission(getPlayer(), triggerType);
}
public void triggerMission(WatcherTriggerType triggerType, int param, int progress) {
getPlayer()
.getServer()
.getBattlePassSystem()
.triggerMission(getPlayer(), triggerType, param, progress);
}
// Handlers
public void takeMissionPoint(List<Integer> missionIdList) {
// Obvious exploit check
if (missionIdList.size() > GameData.getBattlePassMissionDataMap().size()) {
return;
}
List<BattlePassMission> updatedMissions = new ArrayList<>(missionIdList.size());
for (int id : missionIdList) {
// Skip if we dont have this mission
if (!this.hasMission(id)) {
continue;
}
BattlePassMission mission = this.loadMissionById(id);
if (mission.getData() == null) {
this.getMissions().remove(mission.getId());
continue;
}
// Take reward
if (mission.getStatus() == BattlePassMissionStatus.MISSION_STATUS_FINISHED) {
this.addPointsDirectly(mission.getData().getAddPoint(), mission.getData().isCycleRefresh());
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_POINT_TAKEN);
updatedMissions.add(mission);
}
}
if (updatedMissions.size() > 0) {
// Save to db
this.save();
// Packet
getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(updatedMissions));
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
}
private void takeRewardsFromSelectChest(
ItemData rewardItemData, int index, ItemParamData entry, List<GameItem> rewardItems) {
// Sanity checks.
if (rewardItemData.getItemUse().size() < 1) {
return;
}
// Get possible item choices.
String[] choices = rewardItemData.getItemUse().get(0).getUseParam()[0].split(",");
if (choices.length < index) {
return;
}
// Get data for the selected item.
// This depends on the type of chest.
int chosenId = Integer.parseInt(choices[index - 1]);
// For ITEM_USE_ADD_SELECT_ITEM chests, we can directly add the item specified in the chest's
// data.
if (rewardItemData.getItemUse().get(0).getUseOp() == ItemUseOp.ITEM_USE_ADD_SELECT_ITEM) {
GameItem rewardItem =
new GameItem(GameData.getItemDataMap().get(chosenId), entry.getItemCount());
rewardItems.add(rewardItem);
}
// For ITEM_USE_GRANT_SELECT_REWARD chests, we have to again look up reward data.
else if (rewardItemData.getItemUse().get(0).getUseOp()
== ItemUseOp.ITEM_USE_GRANT_SELECT_REWARD) {
RewardData selectedReward = GameData.getRewardDataMap().get(chosenId);
for (var r : selectedReward.getRewardItemList()) {
GameItem rewardItem =
new GameItem(GameData.getItemDataMap().get(r.getItemId()), r.getItemCount());
rewardItems.add(rewardItem);
}
} else {
Grasscutter.getLogger().error("Invalid chest type for BP reward.");
}
}
public void takeReward(List<BattlePassRewardTakeOption> takeOptionList) {
List<BattlePassRewardTakeOption> rewardList = new ArrayList<>();
for (BattlePassRewardTakeOption option : takeOptionList) {
// Duplicate check
if (option.getTag().getRewardId() == 0
|| getTakenRewards().containsKey(option.getTag().getRewardId())) {
continue;
}
// Level check
if (option.getTag().getLevel() > this.getLevel()) {
continue;
}
BattlePassRewardData rewardData =
GameData.getBattlePassRewardDataMap()
.get(GameConstants.BATTLE_PASS_CURRENT_INDEX * 100 + option.getTag().getLevel());
// Sanity check with excel data
if (rewardData.getFreeRewardIdList().contains(option.getTag().getRewardId())) {
rewardList.add(option);
} else if (this.isPaid()
&& rewardData.getPaidRewardIdList().contains(option.getTag().getRewardId())) {
rewardList.add(option);
} else {
Grasscutter.getLogger().info("Not in rewards list: {}", option.getTag().getRewardId());
}
}
// Get rewards
List<GameItem> rewardItems = null;
if (rewardList.size() > 0) {
rewardItems = new ArrayList<>();
for (var option : rewardList) {
var tag = option.getTag();
int index = option.getOptionIdx();
// Make sure we have reward data.
RewardData reward = GameData.getRewardDataMap().get(tag.getRewardId());
if (reward == null) {
continue;
}
// Add reward items.
for (var entry : reward.getRewardItemList()) {
ItemData rewardItemData = GameData.getItemDataMap().get(entry.getItemId());
// Some rewards are chests where the user can select the item they want.
if (rewardItemData.getMaterialType() == MaterialType.MATERIAL_SELECTABLE_CHEST) {
this.takeRewardsFromSelectChest(rewardItemData, index, entry, rewardItems);
}
// All other rewards directly give us the right item.
else {
GameItem rewardItem = new GameItem(rewardItemData, entry.getItemCount());
rewardItems.add(rewardItem);
}
}
// Construct the reward and set as taken.
BattlePassReward bpReward =
new BattlePassReward(
tag.getLevel(),
tag.getRewardId(),
tag.getUnlockStatus() == BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID);
this.getTakenRewards().put(bpReward.getRewardId(), bpReward);
}
// Save to db
this.save();
// Add items and send battle pass schedule packet
getPlayer().getInventory().addItems(rewardItems);
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
getPlayer().sendPacket(new PacketTakeBattlePassRewardRsp(takeOptionList, rewardItems));
}
public int buyLevels(int buyLevel) {
int boughtLevels = Math.min(buyLevel, GameConstants.BATTLE_PASS_MAX_LEVEL - buyLevel);
if (boughtLevels > 0) {
int price = GameConstants.BATTLE_PASS_LEVEL_PRICE * boughtLevels;
if (getPlayer().getPrimogems() < price) {
return 0;
}
this.level += boughtLevels;
this.save();
getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(getPlayer()));
}
return boughtLevels;
}
public void resetDailyMissions() {
var resetMissions = new ArrayList<BattlePassMission>();
for (var mission : this.missions.values()) {
if (mission.getData().getRefreshType() == null
|| mission.getData().getRefreshType()
== BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_DAILY) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_UNFINISHED);
mission.setProgress(0);
resetMissions.add(mission);
}
}
this.getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(resetMissions));
this.getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.getPlayer()));
}
public void resetWeeklyMissions() {
var resetMissions = new ArrayList<BattlePassMission>();
for (var mission : this.missions.values()) {
if (mission.getData().getRefreshType()
== BattlePassMissionRefreshType.BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_UNFINISHED);
mission.setProgress(0);
resetMissions.add(mission);
}
}
this.getPlayer().sendPacket(new PacketBattlePassMissionUpdateNotify(resetMissions));
this.getPlayer().sendPacket(new PacketBattlePassCurScheduleUpdateNotify(this.getPlayer()));
}
//
public BattlePassSchedule getScheduleProto() {
var currentDate = LocalDate.now();
var nextSundayDate =
(currentDate.getDayOfWeek() == DayOfWeek.SUNDAY)
? currentDate
: LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
var nextSundayTime =
LocalDateTime.of(
nextSundayDate.getYear(),
nextSundayDate.getMonthValue(),
nextSundayDate.getDayOfMonth(),
23,
59,
59);
BattlePassSchedule.Builder schedule =
BattlePassSchedule.newBuilder()
.setScheduleId(2700)
.setLevel(this.getLevel())
.setPoint(this.getPoint())
.setBeginTime(0)
.setEndTime(2059483200)
.setIsViewed(this.isViewed())
.setUnlockStatus(
this.isPaid()
? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID
: BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE)
.setPaidPlatformFlags(2) // Not bought on Playstation.
.setCurCyclePoints(this.getCyclePoints())
.setCurCycle(
BattlePassCycle.newBuilder()
.setBeginTime(0)
.setEndTime((int) nextSundayTime.atZone(ZoneId.systemDefault()).toEpochSecond())
.setCycleIdx(3));
for (BattlePassReward reward : getTakenRewards().values()) {
schedule.addRewardTakenList(reward.toProto());
}
return schedule.build();
}
public void save() {
DatabaseHelper.saveBattlePass(this);
}
}

View File

@@ -1,75 +1,75 @@
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.game.props.BattlePassMissionStatus;
@Entity
public class BattlePassMission {
private int id;
private int progress;
private BattlePassMissionStatus status;
@Transient private BattlePassMissionData data;
@Deprecated // Morphia only
public BattlePassMission() {}
public BattlePassMission(int id) {
this.id = id;
}
public int getId() {
return id;
}
public BattlePassMissionData getData() {
if (this.data == null) {
this.data = GameData.getBattlePassMissionDataMap().get(getId());
}
return this.data;
}
public int getProgress() {
return progress;
}
public void setProgress(int value) {
this.progress = value;
}
public void addProgress(int addProgress, int maxProgress) {
this.progress = Math.min(addProgress + this.progress, maxProgress);
}
public BattlePassMissionStatus getStatus() {
if (status == null) status = BattlePassMissionStatus.MISSION_STATUS_UNFINISHED;
return status;
}
public void setStatus(BattlePassMissionStatus status) {
this.status = status;
}
public boolean isFinshed() {
return getStatus().getValue() >= 2;
}
public emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission toProto() {
var protoBuilder =
emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission.newBuilder();
protoBuilder
.setMissionId(getId())
.setCurProgress(getProgress())
.setTotalProgress(getData().getProgress())
.setRewardBattlePassPoint(getData().getAddPoint())
.setMissionStatus(getStatus().getMissionStatus())
.setMissionType(
getData().getRefreshType() == null ? 0 : getData().getRefreshType().getValue());
return protoBuilder.build();
}
}
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.game.props.BattlePassMissionStatus;
@Entity
public class BattlePassMission {
private int id;
private int progress;
private BattlePassMissionStatus status;
@Transient private BattlePassMissionData data;
@Deprecated // Morphia only
public BattlePassMission() {}
public BattlePassMission(int id) {
this.id = id;
}
public int getId() {
return id;
}
public BattlePassMissionData getData() {
if (this.data == null) {
this.data = GameData.getBattlePassMissionDataMap().get(getId());
}
return this.data;
}
public int getProgress() {
return progress;
}
public void setProgress(int value) {
this.progress = value;
}
public void addProgress(int addProgress, int maxProgress) {
this.progress = Math.min(addProgress + this.progress, maxProgress);
}
public BattlePassMissionStatus getStatus() {
if (status == null) status = BattlePassMissionStatus.MISSION_STATUS_UNFINISHED;
return status;
}
public void setStatus(BattlePassMissionStatus status) {
this.status = status;
}
public boolean isFinshed() {
return getStatus().getValue() >= 2;
}
public emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission toProto() {
var protoBuilder =
emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission.newBuilder();
protoBuilder
.setMissionId(getId())
.setCurProgress(getProgress())
.setTotalProgress(getData().getProgress())
.setRewardBattlePassPoint(getData().getAddPoint())
.setMissionStatus(getStatus().getMissionStatus())
.setMissionType(
getData().getRefreshType() == null ? 0 : getData().getRefreshType().getValue());
return protoBuilder.build();
}
}

View File

@@ -1,51 +1,51 @@
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.net.proto.BattlePassRewardTagOuterClass.BattlePassRewardTag;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
@Entity
public class BattlePassReward {
private int level;
private int rewardId;
private boolean paid;
@Transient private BattlePassMissionData data;
@Deprecated // Morphia only
public BattlePassReward() {}
public BattlePassReward(int level, int rewardId, boolean paid) {
this.level = level;
this.rewardId = rewardId;
this.paid = paid;
}
public int getLevel() {
return level;
}
public int getRewardId() {
return rewardId;
}
public boolean isPaid() {
return paid;
}
public BattlePassRewardTag toProto() {
var protoBuilder = BattlePassRewardTag.newBuilder();
protoBuilder
.setLevel(this.getLevel())
.setRewardId(this.getRewardId())
.setUnlockStatus(
this.isPaid()
? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID
: BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE);
return protoBuilder.build();
}
}
package emu.grasscutter.game.battlepass;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.net.proto.BattlePassRewardTagOuterClass.BattlePassRewardTag;
import emu.grasscutter.net.proto.BattlePassUnlockStatusOuterClass.BattlePassUnlockStatus;
@Entity
public class BattlePassReward {
private int level;
private int rewardId;
private boolean paid;
@Transient private BattlePassMissionData data;
@Deprecated // Morphia only
public BattlePassReward() {}
public BattlePassReward(int level, int rewardId, boolean paid) {
this.level = level;
this.rewardId = rewardId;
this.paid = paid;
}
public int getLevel() {
return level;
}
public int getRewardId() {
return rewardId;
}
public boolean isPaid() {
return paid;
}
public BattlePassRewardTag toProto() {
var protoBuilder = BattlePassRewardTag.newBuilder();
protoBuilder
.setLevel(this.getLevel())
.setRewardId(this.getRewardId())
.setUnlockStatus(
this.isPaid()
? BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_PAID
: BattlePassUnlockStatus.BATTLE_PASS_UNLOCK_STATUS_FREE);
return protoBuilder.build();
}
}

View File

@@ -1,80 +1,80 @@
package emu.grasscutter.game.battlepass;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BattlePassSystem extends BaseGameSystem {
private final Map<WatcherTriggerType, List<BattlePassMissionData>> cachedTriggers;
// BP Mission manager for the server, contains cached triggers so we dont have to load it for each
// player
public BattlePassSystem(GameServer server) {
super(server);
this.cachedTriggers = new HashMap<>();
for (BattlePassMissionData missionData : GameData.getBattlePassMissionDataMap().values()) {
if (missionData.isValidRefreshType()) {
List<BattlePassMissionData> triggerList =
getTriggers().computeIfAbsent(missionData.getTriggerType(), e -> new ArrayList<>());
triggerList.add(missionData);
}
}
}
public GameServer getServer() {
return server;
}
private Map<WatcherTriggerType, List<BattlePassMissionData>> getTriggers() {
return cachedTriggers;
}
public void triggerMission(Player player, WatcherTriggerType triggerType) {
triggerMission(player, triggerType, 0, 1);
}
public void triggerMission(
Player player, WatcherTriggerType triggerType, int param, int progress) {
List<BattlePassMissionData> triggerList = getTriggers().get(triggerType);
if (triggerList == null || triggerList.isEmpty()) return;
for (BattlePassMissionData data : triggerList) {
// Skip params check if param == 0
if (param != 0) {
if (!data.getMainParams().contains(param)) {
continue;
}
}
// Get mission from player, if it doesnt exist, then we make one
BattlePassMission mission = player.getBattlePassManager().loadMissionById(data.getId());
if (mission.isFinshed()) continue;
// Add progress
mission.addProgress(progress, data.getProgress());
if (mission.getProgress() >= data.getProgress()) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_FINISHED);
}
// Save to db
player.getBattlePassManager().save();
// Packet
player.sendPacket(new PacketBattlePassMissionUpdateNotify(mission));
}
}
}
package emu.grasscutter.game.battlepass;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.BattlePassMissionData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.BattlePassMissionStatus;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketBattlePassMissionUpdateNotify;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class BattlePassSystem extends BaseGameSystem {
private final Map<WatcherTriggerType, List<BattlePassMissionData>> cachedTriggers;
// BP Mission manager for the server, contains cached triggers so we dont have to load it for each
// player
public BattlePassSystem(GameServer server) {
super(server);
this.cachedTriggers = new HashMap<>();
for (BattlePassMissionData missionData : GameData.getBattlePassMissionDataMap().values()) {
if (missionData.isValidRefreshType()) {
List<BattlePassMissionData> triggerList =
getTriggers().computeIfAbsent(missionData.getTriggerType(), e -> new ArrayList<>());
triggerList.add(missionData);
}
}
}
public GameServer getServer() {
return server;
}
private Map<WatcherTriggerType, List<BattlePassMissionData>> getTriggers() {
return cachedTriggers;
}
public void triggerMission(Player player, WatcherTriggerType triggerType) {
triggerMission(player, triggerType, 0, 1);
}
public void triggerMission(
Player player, WatcherTriggerType triggerType, int param, int progress) {
List<BattlePassMissionData> triggerList = getTriggers().get(triggerType);
if (triggerList == null || triggerList.isEmpty()) return;
for (BattlePassMissionData data : triggerList) {
// Skip params check if param == 0
if (param != 0) {
if (!data.getMainParams().contains(param)) {
continue;
}
}
// Get mission from player, if it doesnt exist, then we make one
BattlePassMission mission = player.getBattlePassManager().loadMissionById(data.getId());
if (mission.isFinshed()) continue;
// Add progress
mission.addProgress(progress, data.getProgress());
if (mission.getProgress() >= data.getProgress()) {
mission.setStatus(BattlePassMissionStatus.MISSION_STATUS_FINISHED);
}
// Save to db
player.getBattlePassManager().save();
// Packet
player.sendPacket(new PacketBattlePassMissionUpdateNotify(mission));
}
}
}

View File

@@ -1,215 +1,215 @@
package emu.grasscutter.game.chat;
import static emu.grasscutter.config.Configuration.GAME_INFO;
import emu.grasscutter.GameConstants;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketPlayerChatNotify;
import emu.grasscutter.server.packet.send.PacketPrivateChatNotify;
import emu.grasscutter.server.packet.send.PacketPullPrivateChatRsp;
import emu.grasscutter.server.packet.send.PacketPullRecentChatRsp;
import emu.grasscutter.utils.Utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
public class ChatSystem implements ChatSystemHandler {
static final String PREFIXES = "[/!]";
static final Pattern RE_PREFIXES = Pattern.compile(PREFIXES);
static final Pattern RE_COMMANDS = Pattern.compile("\n" + PREFIXES);
// We store the chat history for ongoing sessions in the form
// user id -> chat partner id -> [messages]
private final Map<Integer, Map<Integer, List<ChatInfo>>> history = new HashMap<>();
private final GameServer server;
public ChatSystem(GameServer server) {
this.server = server;
}
public GameServer getServer() {
return server;
}
private boolean tryInvokeCommand(Player sender, Player target, String rawMessage) {
if (!RE_PREFIXES.matcher(rawMessage.substring(0, 1)).matches()) return false;
for (String line : rawMessage.substring(1).split("\n[/!]"))
CommandMap.getInstance().invoke(sender, target, line);
return true;
}
/********************
* Chat history handling
********************/
private void putInHistory(int uid, int partnerId, ChatInfo info) {
this.history
.computeIfAbsent(uid, x -> new HashMap<>())
.computeIfAbsent(partnerId, x -> new ArrayList<>())
.add(info);
}
public void clearHistoryOnLogout(Player player) {
this.history.remove(player.getUid());
}
public void handlePullPrivateChatReq(Player player, int partnerId) {
var chatHistory =
this.history
.computeIfAbsent(player.getUid(), x -> new HashMap<>())
.computeIfAbsent(partnerId, x -> new ArrayList<>());
player.sendPacket(new PacketPullPrivateChatRsp(chatHistory));
}
public void handlePullRecentChatReq(Player player) {
// If this user has no chat history yet, create it by sending the server welcome messages.
if (!this.history
.computeIfAbsent(player.getUid(), x -> new HashMap<>())
.containsKey(GameConstants.SERVER_CONSOLE_UID)) {
this.sendServerWelcomeMessages(player);
}
// For now, we send the list three messages from the server for the recent chat history.
// This matches the previous behavior, but ultimately, we should probably keep track of the last
// chat partner
// for every given player and return the last messages exchanged with that partner.
int historyLength =
this.history.get(player.getUid()).get(GameConstants.SERVER_CONSOLE_UID).size();
var messages =
this.history
.get(player.getUid())
.get(GameConstants.SERVER_CONSOLE_UID)
.subList(Math.max(historyLength - 3, 0), historyLength);
player.sendPacket(new PacketPullRecentChatRsp(messages));
}
/********************
* Sending messages
********************/
public void sendPrivateMessageFromServer(int targetUid, String message) {
// Sanity checks.
if (message == null || message.length() == 0) {
return;
}
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null) {
return;
}
// Create chat packet and put in history.
var packet = new PacketPrivateChatNotify(GameConstants.SERVER_CONSOLE_UID, targetUid, message);
putInHistory(targetUid, GameConstants.SERVER_CONSOLE_UID, packet.getChatInfo());
// Send.
target.sendPacket(packet);
}
public void sendPrivateMessageFromServer(int targetUid, int emote) {
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null) {
return;
}
// Create chat packet and put in history.
var packet = new PacketPrivateChatNotify(GameConstants.SERVER_CONSOLE_UID, targetUid, emote);
putInHistory(targetUid, GameConstants.SERVER_CONSOLE_UID, packet.getChatInfo());
// Send.
target.sendPacket(packet);
}
public void sendPrivateMessage(Player player, int targetUid, String message) {
// Sanity checks.
if (message == null || message.length() == 0) {
return;
}
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null && targetUid != GameConstants.SERVER_CONSOLE_UID) {
return;
}
// Create chat packet.
var packet = new PacketPrivateChatNotify(player.getUid(), targetUid, message);
// Send and put in history.
player.sendPacket(packet);
putInHistory(player.getUid(), targetUid, packet.getChatInfo());
// Check if command
boolean isCommand = tryInvokeCommand(player, target, message);
if ((target != null) && (!isCommand)) {
target.sendPacket(packet);
putInHistory(targetUid, player.getUid(), packet.getChatInfo());
}
}
public void sendPrivateMessage(Player player, int targetUid, int emote) {
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null && targetUid != GameConstants.SERVER_CONSOLE_UID) {
return;
}
// Create chat packet.
var packet = new PacketPrivateChatNotify(player.getUid(), target.getUid(), emote);
// Send and put is history.
player.sendPacket(packet);
putInHistory(player.getUid(), targetUid, packet.getChatInfo());
if (target != null) {
target.sendPacket(packet);
putInHistory(targetUid, player.getUid(), packet.getChatInfo());
}
}
public void sendTeamMessage(Player player, int channel, String message) {
// Sanity checks
if (message == null || message.length() == 0) {
return;
}
// Check if command
if (tryInvokeCommand(player, null, message)) {
return;
}
// Create and send chat packet
player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, message));
}
public void sendTeamMessage(Player player, int channel, int icon) {
// Create and send chat packet
player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, icon));
}
/********************
* Welcome messages
********************/
private void sendServerWelcomeMessages(Player player) {
var joinOptions = GAME_INFO.joinOptions;
if (joinOptions.welcomeEmotes != null && joinOptions.welcomeEmotes.length > 0) {
this.sendPrivateMessageFromServer(
player.getUid(),
joinOptions.welcomeEmotes[Utils.randomRange(0, joinOptions.welcomeEmotes.length - 1)]);
}
if (joinOptions.welcomeMessage != null && joinOptions.welcomeMessage.length() > 0) {
this.sendPrivateMessageFromServer(player.getUid(), joinOptions.welcomeMessage);
}
}
}
package emu.grasscutter.game.chat;
import static emu.grasscutter.config.Configuration.GAME_INFO;
import emu.grasscutter.GameConstants;
import emu.grasscutter.command.CommandMap;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketPlayerChatNotify;
import emu.grasscutter.server.packet.send.PacketPrivateChatNotify;
import emu.grasscutter.server.packet.send.PacketPullPrivateChatRsp;
import emu.grasscutter.server.packet.send.PacketPullRecentChatRsp;
import emu.grasscutter.utils.Utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
public class ChatSystem implements ChatSystemHandler {
static final String PREFIXES = "[/!]";
static final Pattern RE_PREFIXES = Pattern.compile(PREFIXES);
static final Pattern RE_COMMANDS = Pattern.compile("\n" + PREFIXES);
// We store the chat history for ongoing sessions in the form
// user id -> chat partner id -> [messages]
private final Map<Integer, Map<Integer, List<ChatInfo>>> history = new HashMap<>();
private final GameServer server;
public ChatSystem(GameServer server) {
this.server = server;
}
public GameServer getServer() {
return server;
}
private boolean tryInvokeCommand(Player sender, Player target, String rawMessage) {
if (!RE_PREFIXES.matcher(rawMessage.substring(0, 1)).matches()) return false;
for (String line : rawMessage.substring(1).split("\n[/!]"))
CommandMap.getInstance().invoke(sender, target, line);
return true;
}
/********************
* Chat history handling
********************/
private void putInHistory(int uid, int partnerId, ChatInfo info) {
this.history
.computeIfAbsent(uid, x -> new HashMap<>())
.computeIfAbsent(partnerId, x -> new ArrayList<>())
.add(info);
}
public void clearHistoryOnLogout(Player player) {
this.history.remove(player.getUid());
}
public void handlePullPrivateChatReq(Player player, int partnerId) {
var chatHistory =
this.history
.computeIfAbsent(player.getUid(), x -> new HashMap<>())
.computeIfAbsent(partnerId, x -> new ArrayList<>());
player.sendPacket(new PacketPullPrivateChatRsp(chatHistory));
}
public void handlePullRecentChatReq(Player player) {
// If this user has no chat history yet, create it by sending the server welcome messages.
if (!this.history
.computeIfAbsent(player.getUid(), x -> new HashMap<>())
.containsKey(GameConstants.SERVER_CONSOLE_UID)) {
this.sendServerWelcomeMessages(player);
}
// For now, we send the list three messages from the server for the recent chat history.
// This matches the previous behavior, but ultimately, we should probably keep track of the last
// chat partner
// for every given player and return the last messages exchanged with that partner.
int historyLength =
this.history.get(player.getUid()).get(GameConstants.SERVER_CONSOLE_UID).size();
var messages =
this.history
.get(player.getUid())
.get(GameConstants.SERVER_CONSOLE_UID)
.subList(Math.max(historyLength - 3, 0), historyLength);
player.sendPacket(new PacketPullRecentChatRsp(messages));
}
/********************
* Sending messages
********************/
public void sendPrivateMessageFromServer(int targetUid, String message) {
// Sanity checks.
if (message == null || message.length() == 0) {
return;
}
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null) {
return;
}
// Create chat packet and put in history.
var packet = new PacketPrivateChatNotify(GameConstants.SERVER_CONSOLE_UID, targetUid, message);
putInHistory(targetUid, GameConstants.SERVER_CONSOLE_UID, packet.getChatInfo());
// Send.
target.sendPacket(packet);
}
public void sendPrivateMessageFromServer(int targetUid, int emote) {
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null) {
return;
}
// Create chat packet and put in history.
var packet = new PacketPrivateChatNotify(GameConstants.SERVER_CONSOLE_UID, targetUid, emote);
putInHistory(targetUid, GameConstants.SERVER_CONSOLE_UID, packet.getChatInfo());
// Send.
target.sendPacket(packet);
}
public void sendPrivateMessage(Player player, int targetUid, String message) {
// Sanity checks.
if (message == null || message.length() == 0) {
return;
}
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null && targetUid != GameConstants.SERVER_CONSOLE_UID) {
return;
}
// Create chat packet.
var packet = new PacketPrivateChatNotify(player.getUid(), targetUid, message);
// Send and put in history.
player.sendPacket(packet);
putInHistory(player.getUid(), targetUid, packet.getChatInfo());
// Check if command
boolean isCommand = tryInvokeCommand(player, target, message);
if ((target != null) && (!isCommand)) {
target.sendPacket(packet);
putInHistory(targetUid, player.getUid(), packet.getChatInfo());
}
}
public void sendPrivateMessage(Player player, int targetUid, int emote) {
// Get target.
Player target = getServer().getPlayerByUid(targetUid);
if (target == null && targetUid != GameConstants.SERVER_CONSOLE_UID) {
return;
}
// Create chat packet.
var packet = new PacketPrivateChatNotify(player.getUid(), target.getUid(), emote);
// Send and put is history.
player.sendPacket(packet);
putInHistory(player.getUid(), targetUid, packet.getChatInfo());
if (target != null) {
target.sendPacket(packet);
putInHistory(targetUid, player.getUid(), packet.getChatInfo());
}
}
public void sendTeamMessage(Player player, int channel, String message) {
// Sanity checks
if (message == null || message.length() == 0) {
return;
}
// Check if command
if (tryInvokeCommand(player, null, message)) {
return;
}
// Create and send chat packet
player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, message));
}
public void sendTeamMessage(Player player, int channel, int icon) {
// Create and send chat packet
player.getWorld().broadcastPacket(new PacketPlayerChatNotify(player, channel, icon));
}
/********************
* Welcome messages
********************/
private void sendServerWelcomeMessages(Player player) {
var joinOptions = GAME_INFO.joinOptions;
if (joinOptions.welcomeEmotes != null && joinOptions.welcomeEmotes.length > 0) {
this.sendPrivateMessageFromServer(
player.getUid(),
joinOptions.welcomeEmotes[Utils.randomRange(0, joinOptions.welcomeEmotes.length - 1)]);
}
if (joinOptions.welcomeMessage != null && joinOptions.welcomeMessage.length() > 0) {
this.sendPrivateMessageFromServer(player.getUid(), joinOptions.welcomeMessage);
}
}
}

View File

@@ -1,26 +1,26 @@
package emu.grasscutter.game.chat;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameServer;
public interface ChatSystemHandler {
GameServer getServer();
void sendPrivateMessage(Player player, int targetUid, String message);
void sendPrivateMessage(Player player, int targetUid, int emote);
void sendTeamMessage(Player player, int channel, String message);
void sendTeamMessage(Player player, int channel, int icon);
void sendPrivateMessageFromServer(int targetUid, String message);
void sendPrivateMessageFromServer(int targetUid, int emote);
void handlePullPrivateChatReq(Player player, int targetUid);
void clearHistoryOnLogout(Player player);
void handlePullRecentChatReq(Player player);
}
package emu.grasscutter.game.chat;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.game.GameServer;
public interface ChatSystemHandler {
GameServer getServer();
void sendPrivateMessage(Player player, int targetUid, String message);
void sendPrivateMessage(Player player, int targetUid, int emote);
void sendTeamMessage(Player player, int channel, String message);
void sendTeamMessage(Player player, int channel, int icon);
void sendPrivateMessageFromServer(int targetUid, String message);
void sendPrivateMessageFromServer(int targetUid, int emote);
void handlePullPrivateChatReq(Player player, int targetUid);
void clearHistoryOnLogout(Player player);
void handlePullRecentChatReq(Player player);
}

View File

@@ -1,143 +1,143 @@
package emu.grasscutter.game.combine;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.CombineData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.RetcodeOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketCombineFormulaDataNotify;
import emu.grasscutter.server.packet.send.PacketCombineRsp;
import emu.grasscutter.server.packet.send.PacketReliquaryDecomposeRsp;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.ArrayList;
import java.util.List;
public class CombineManger extends BaseGameSystem {
private static final Int2ObjectMap<List<Integer>> reliquaryDecomposeData =
new Int2ObjectOpenHashMap<>();
public CombineManger(GameServer server) {
super(server);
}
public static void initialize() {
// Read the data we need for strongbox.
try {
DataLoader.loadList("ReliquaryDecompose.json", ReliquaryDecomposeEntry.class)
.forEach(
entry -> {
reliquaryDecomposeData.put(entry.getConfigId(), entry.getItems());
});
Grasscutter.getLogger()
.debug("Loaded {} reliquary decompose entries.", reliquaryDecomposeData.size());
} catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load reliquary decompose data.", ex);
}
}
public boolean unlockCombineDiagram(Player player, int combineId) {
if (!player.getUnlockedCombines().add(combineId)) {
return false; // Already unlocked
}
// Tell the client that this diagram is now unlocked and add the unlocked item to the player.
player.sendPacket(new PacketCombineFormulaDataNotify(combineId));
return true;
}
public CombineResult combineItem(Player player, int cid, int count) {
// check config exist
if (!GameData.getCombineDataMap().containsKey(cid)) {
player.getWorld().getHost().sendPacket(new PacketCombineRsp());
return null;
}
CombineData combineData = GameData.getCombineDataMap().get(cid);
if (combineData.getPlayerLevel() > player.getLevel()) {
return null;
}
// consume items
List<ItemParamData> material = new ArrayList<>(combineData.getMaterialItems());
material.add(new ItemParamData(202, combineData.getScoinCost()));
boolean success = player.getInventory().payItems(material, count, ActionReason.Combine);
// abort if not enough material
if (!success) {
player.sendPacket(
new PacketCombineRsp(RetcodeOuterClass.Retcode.RET_ITEM_COMBINE_COUNT_NOT_ENOUGH_VALUE));
}
// make the result
player
.getInventory()
.addItem(combineData.getResultItemId(), combineData.getResultItemCount() * count);
CombineResult result = new CombineResult();
result.setMaterial(List.of());
result.setResult(
List.of(
new ItemParamData(
combineData.getResultItemId(), combineData.getResultItemCount() * count)));
// TODO lucky characters
result.setExtra(List.of());
result.setBack(List.of());
return result;
}
public synchronized void decomposeReliquaries(
Player player, int configId, int count, List<Long> input) {
// Check if the configId is legal.
List<Integer> possibleDrops = reliquaryDecomposeData.get(configId);
if (possibleDrops == null) {
player.sendPacket(
new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
// Check if the number of input items matches the output count.
if (input.size() != count * 3) {
player.sendPacket(
new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
// Check if all the input reliquaries actually are in the player's inventory.
for (long guid : input) {
if (player.getInventory().getItemByGuid(guid) == null) {
player.sendPacket(
new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
}
// Delete the input reliquaries.
for (long guid : input) {
player.getInventory().removeItem(guid);
}
// Generate outoput reliquaries.
List<Long> resultItems = new ArrayList<>();
for (int i = 0; i < count; i++) {
int itemId = Utils.drawRandomListElement(possibleDrops);
GameItem newReliquary = new GameItem(itemId, 1);
player.getInventory().addItem(newReliquary);
resultItems.add(newReliquary.getGuid());
}
// Send packet.
player.sendPacket(new PacketReliquaryDecomposeRsp(resultItems));
}
}
package emu.grasscutter.game.combine;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.CombineData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.RetcodeOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketCombineFormulaDataNotify;
import emu.grasscutter.server.packet.send.PacketCombineRsp;
import emu.grasscutter.server.packet.send.PacketReliquaryDecomposeRsp;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.ArrayList;
import java.util.List;
public class CombineManger extends BaseGameSystem {
private static final Int2ObjectMap<List<Integer>> reliquaryDecomposeData =
new Int2ObjectOpenHashMap<>();
public CombineManger(GameServer server) {
super(server);
}
public static void initialize() {
// Read the data we need for strongbox.
try {
DataLoader.loadList("ReliquaryDecompose.json", ReliquaryDecomposeEntry.class)
.forEach(
entry -> {
reliquaryDecomposeData.put(entry.getConfigId(), entry.getItems());
});
Grasscutter.getLogger()
.debug("Loaded {} reliquary decompose entries.", reliquaryDecomposeData.size());
} catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load reliquary decompose data.", ex);
}
}
public boolean unlockCombineDiagram(Player player, int combineId) {
if (!player.getUnlockedCombines().add(combineId)) {
return false; // Already unlocked
}
// Tell the client that this diagram is now unlocked and add the unlocked item to the player.
player.sendPacket(new PacketCombineFormulaDataNotify(combineId));
return true;
}
public CombineResult combineItem(Player player, int cid, int count) {
// check config exist
if (!GameData.getCombineDataMap().containsKey(cid)) {
player.getWorld().getHost().sendPacket(new PacketCombineRsp());
return null;
}
CombineData combineData = GameData.getCombineDataMap().get(cid);
if (combineData.getPlayerLevel() > player.getLevel()) {
return null;
}
// consume items
List<ItemParamData> material = new ArrayList<>(combineData.getMaterialItems());
material.add(new ItemParamData(202, combineData.getScoinCost()));
boolean success = player.getInventory().payItems(material, count, ActionReason.Combine);
// abort if not enough material
if (!success) {
player.sendPacket(
new PacketCombineRsp(RetcodeOuterClass.Retcode.RET_ITEM_COMBINE_COUNT_NOT_ENOUGH_VALUE));
}
// make the result
player
.getInventory()
.addItem(combineData.getResultItemId(), combineData.getResultItemCount() * count);
CombineResult result = new CombineResult();
result.setMaterial(List.of());
result.setResult(
List.of(
new ItemParamData(
combineData.getResultItemId(), combineData.getResultItemCount() * count)));
// TODO lucky characters
result.setExtra(List.of());
result.setBack(List.of());
return result;
}
public synchronized void decomposeReliquaries(
Player player, int configId, int count, List<Long> input) {
// Check if the configId is legal.
List<Integer> possibleDrops = reliquaryDecomposeData.get(configId);
if (possibleDrops == null) {
player.sendPacket(
new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
// Check if the number of input items matches the output count.
if (input.size() != count * 3) {
player.sendPacket(
new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
// Check if all the input reliquaries actually are in the player's inventory.
for (long guid : input) {
if (player.getInventory().getItemByGuid(guid) == null) {
player.sendPacket(
new PacketReliquaryDecomposeRsp(Retcode.RET_RELIQUARY_DECOMPOSE_PARAM_ERROR));
return;
}
}
// Delete the input reliquaries.
for (long guid : input) {
player.getInventory().removeItem(guid);
}
// Generate outoput reliquaries.
List<Long> resultItems = new ArrayList<>();
for (int i = 0; i < count; i++) {
int itemId = Utils.drawRandomListElement(possibleDrops);
GameItem newReliquary = new GameItem(itemId, 1);
player.getInventory().addItem(newReliquary);
resultItems.add(newReliquary.getGuid());
}
// Send packet.
player.sendPacket(new PacketReliquaryDecomposeRsp(resultItems));
}
}

View File

@@ -1,43 +1,43 @@
package emu.grasscutter.game.combine;
import emu.grasscutter.data.common.ItemParamData;
import java.util.List;
public class CombineResult {
private List<ItemParamData> material;
private List<ItemParamData> result;
private List<ItemParamData> extra;
private List<ItemParamData> back;
public List<ItemParamData> getMaterial() {
return material;
}
public void setMaterial(List<ItemParamData> material) {
this.material = material;
}
public List<ItemParamData> getResult() {
return result;
}
public void setResult(List<ItemParamData> result) {
this.result = result;
}
public List<ItemParamData> getExtra() {
return extra;
}
public void setExtra(List<ItemParamData> extra) {
this.extra = extra;
}
public List<ItemParamData> getBack() {
return back;
}
public void setBack(List<ItemParamData> back) {
this.back = back;
}
}
package emu.grasscutter.game.combine;
import emu.grasscutter.data.common.ItemParamData;
import java.util.List;
public class CombineResult {
private List<ItemParamData> material;
private List<ItemParamData> result;
private List<ItemParamData> extra;
private List<ItemParamData> back;
public List<ItemParamData> getMaterial() {
return material;
}
public void setMaterial(List<ItemParamData> material) {
this.material = material;
}
public List<ItemParamData> getResult() {
return result;
}
public void setResult(List<ItemParamData> result) {
this.result = result;
}
public List<ItemParamData> getExtra() {
return extra;
}
public void setExtra(List<ItemParamData> extra) {
this.extra = extra;
}
public List<ItemParamData> getBack() {
return back;
}
public void setBack(List<ItemParamData> back) {
this.back = back;
}
}

View File

@@ -1,24 +1,24 @@
package emu.grasscutter.game.combine;
import java.util.List;
public class ReliquaryDecomposeEntry {
private int configId;
private List<Integer> items;
public int getConfigId() {
return configId;
}
public void setConfigId(int configId) {
this.configId = configId;
}
public List<Integer> getItems() {
return items;
}
public void setItems(List<Integer> items) {
this.items = items;
}
}
package emu.grasscutter.game.combine;
import java.util.List;
public class ReliquaryDecomposeEntry {
private int configId;
private List<Integer> items;
public int getConfigId() {
return configId;
}
public void setConfigId(int configId) {
this.configId = configId;
}
public List<Integer> getItems() {
return items;
}
public void setItems(List<Integer> items) {
this.items = items;
}
}

View File

@@ -1,16 +1,16 @@
package emu.grasscutter.game.drop;
import java.util.List;
public class DropInfo {
private int monsterId;
private List<DropData> dropDataList;
public int getMonsterId() {
return monsterId;
}
public List<DropData> getDropDataList() {
return dropDataList;
}
}
package emu.grasscutter.game.drop;
import java.util.List;
public class DropInfo {
private int monsterId;
private List<DropData> dropDataList;
public int getMonsterId() {
return monsterId;
}
public List<DropData> getDropDataList() {
return dropDataList;
}
}

View File

@@ -1,24 +1,24 @@
package emu.grasscutter.game.dungeons;
import java.util.List;
public class DungeonDrop {
private int dungeonId;
private List<DungeonDropEntry> drops;
public int getDungeonId() {
return dungeonId;
}
public void setDungeonId(int dungeonId) {
this.dungeonId = dungeonId;
}
public List<DungeonDropEntry> getDrops() {
return drops;
}
public void setDrops(List<DungeonDropEntry> drops) {
this.drops = drops;
}
}
package emu.grasscutter.game.dungeons;
import java.util.List;
public class DungeonDrop {
private int dungeonId;
private List<DungeonDropEntry> drops;
public int getDungeonId() {
return dungeonId;
}
public void setDungeonId(int dungeonId) {
this.dungeonId = dungeonId;
}
public List<DungeonDropEntry> getDrops() {
return drops;
}
public void setDrops(List<DungeonDropEntry> drops) {
this.drops = drops;
}
}

View File

@@ -1,51 +1,51 @@
package emu.grasscutter.game.dungeons;
import java.util.List;
public class DungeonDropEntry {
private List<Integer> counts;
private List<Integer> items;
private List<Integer> probabilities;
private List<Integer> itemProbabilities;
private boolean mpDouble;
public List<Integer> getCounts() {
return counts;
}
public void setCounts(List<Integer> counts) {
this.counts = counts;
}
public List<Integer> getItems() {
return items;
}
public void setItems(List<Integer> items) {
this.items = items;
}
public List<Integer> getProbabilities() {
return probabilities;
}
public void setProbabilities(List<Integer> probabilities) {
this.probabilities = probabilities;
}
public List<Integer> getItemProbabilities() {
return itemProbabilities;
}
public void setItemProbabilities(List<Integer> itemProbabilities) {
this.itemProbabilities = itemProbabilities;
}
public boolean isMpDouble() {
return mpDouble;
}
public void setMpDouble(boolean mpDouble) {
this.mpDouble = mpDouble;
}
}
package emu.grasscutter.game.dungeons;
import java.util.List;
public class DungeonDropEntry {
private List<Integer> counts;
private List<Integer> items;
private List<Integer> probabilities;
private List<Integer> itemProbabilities;
private boolean mpDouble;
public List<Integer> getCounts() {
return counts;
}
public void setCounts(List<Integer> counts) {
this.counts = counts;
}
public List<Integer> getItems() {
return items;
}
public void setItems(List<Integer> items) {
this.items = items;
}
public List<Integer> getProbabilities() {
return probabilities;
}
public void setProbabilities(List<Integer> probabilities) {
this.probabilities = probabilities;
}
public List<Integer> getItemProbabilities() {
return itemProbabilities;
}
public void setItemProbabilities(List<Integer> itemProbabilities) {
this.itemProbabilities = itemProbabilities;
}
public boolean isMpDouble() {
return mpDouble;
}
public void setMpDouble(boolean mpDouble) {
this.mpDouble = mpDouble;
}
}

View File

@@ -1,24 +1,24 @@
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
public class KillGadgetTrigger extends ChallengeTrigger {
@Override
public void onBegin(WorldChallenge challenge) {
challenge
.getScene()
.broadcastPacket(new PacketChallengeDataNotify(challenge, 2, challenge.getScore().get()));
}
@Override
public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget) {
var newScore = challenge.increaseScore();
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, newScore));
if (newScore >= challenge.getGoal()) {
challenge.done();
}
}
}
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
public class KillGadgetTrigger extends ChallengeTrigger {
@Override
public void onBegin(WorldChallenge challenge) {
challenge
.getScene()
.broadcastPacket(new PacketChallengeDataNotify(challenge, 2, challenge.getScore().get()));
}
@Override
public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget) {
var newScore = challenge.increaseScore();
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, newScore));
if (newScore >= challenge.getGoal()) {
challenge.done();
}
}
}

View File

@@ -1,143 +1,143 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.GadgetBornTypeOuterClass.GadgetBornType;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import lombok.Getter;
public class EntityItem extends EntityBaseGadget {
@Getter private final GameItem item;
@Getter private final long guid;
@Getter private final boolean share;
public EntityItem(Scene scene, Player player, ItemData itemData, Position pos, int count) {
this(scene, player, itemData, pos, count, true);
}
// In official game, some drop items are shared to all players, and some other items are
// independent to all players
// For example, if you killed a monster in MP mode, all players could get drops but rarity and
// number of them are different
// but if you broke regional mine, when someone picked up the drop then it disappeared
public EntityItem(
Scene scene, Player player, ItemData itemData, Position pos, int count, boolean share) {
super(scene, pos, null);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.guid =
player == null ? scene.getWorld().getHost().getNextGameGuid() : player.getNextGameGuid();
this.item = new GameItem(itemData, count);
this.share = share;
}
public ItemData getItemData() {
return this.getItem().getItemData();
}
public int getCount() {
return this.getItem().getCount();
}
@Override
public int getGadgetId() {
return this.getItemData().getGadgetId();
}
@Override
public Int2FloatMap getFightProperties() {
return null;
}
@Override
public void onInteract(Player player, GadgetInteractReq interactReq) {
// check drop owner to avoid someone picked up item in others' world
if (!this.isShare()) {
int dropOwner = (int) (this.getGuid() >> 32);
if (dropOwner != player.getUid()) {
return;
}
}
this.getScene().removeEntity(this);
GameItem item = new GameItem(this.getItemData(), this.getCount());
// Add to inventory
boolean success = player.getInventory().addItem(item, ActionReason.SubfieldDrop);
if (success) {
if (!this.isShare()) { // not shared drop
player.sendPacket(new PacketGadgetInteractRsp(this, InteractType.INTERACT_TYPE_PICK_ITEM));
} else {
this.getScene()
.broadcastPacket(
new PacketGadgetInteractRsp(this, InteractType.INTERACT_TYPE_PICK_ITEM));
}
}
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority =
EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(
SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneEntityInfo.Builder entityInfo =
SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(
MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair =
PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
.build();
entityInfo.addPropList(pair);
SceneGadgetInfo.Builder gadgetInfo =
SceneGadgetInfo.newBuilder()
.setGadgetId(this.getItemData().getGadgetId())
.setTrifleItem(this.getItem().toProto())
.setBornType(GadgetBornType.GADGET_BORN_TYPE_IN_AIR)
.setAuthorityPeerId(this.getWorld().getHostPeerId())
.setIsEnableInteract(true);
entityInfo.setGadget(gadgetInfo);
return entityInfo.build();
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.GadgetBornTypeOuterClass.GadgetBornType;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import lombok.Getter;
public class EntityItem extends EntityBaseGadget {
@Getter private final GameItem item;
@Getter private final long guid;
@Getter private final boolean share;
public EntityItem(Scene scene, Player player, ItemData itemData, Position pos, int count) {
this(scene, player, itemData, pos, count, true);
}
// In official game, some drop items are shared to all players, and some other items are
// independent to all players
// For example, if you killed a monster in MP mode, all players could get drops but rarity and
// number of them are different
// but if you broke regional mine, when someone picked up the drop then it disappeared
public EntityItem(
Scene scene, Player player, ItemData itemData, Position pos, int count, boolean share) {
super(scene, pos, null);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.guid =
player == null ? scene.getWorld().getHost().getNextGameGuid() : player.getNextGameGuid();
this.item = new GameItem(itemData, count);
this.share = share;
}
public ItemData getItemData() {
return this.getItem().getItemData();
}
public int getCount() {
return this.getItem().getCount();
}
@Override
public int getGadgetId() {
return this.getItemData().getGadgetId();
}
@Override
public Int2FloatMap getFightProperties() {
return null;
}
@Override
public void onInteract(Player player, GadgetInteractReq interactReq) {
// check drop owner to avoid someone picked up item in others' world
if (!this.isShare()) {
int dropOwner = (int) (this.getGuid() >> 32);
if (dropOwner != player.getUid()) {
return;
}
}
this.getScene().removeEntity(this);
GameItem item = new GameItem(this.getItemData(), this.getCount());
// Add to inventory
boolean success = player.getInventory().addItem(item, ActionReason.SubfieldDrop);
if (success) {
if (!this.isShare()) { // not shared drop
player.sendPacket(new PacketGadgetInteractRsp(this, InteractType.INTERACT_TYPE_PICK_ITEM));
} else {
this.getScene()
.broadcastPacket(
new PacketGadgetInteractRsp(this, InteractType.INTERACT_TYPE_PICK_ITEM));
}
}
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority =
EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(
SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneEntityInfo.Builder entityInfo =
SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(
MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair =
PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
.build();
entityInfo.addPropList(pair);
SceneGadgetInfo.Builder gadgetInfo =
SceneGadgetInfo.newBuilder()
.setGadgetId(this.getItemData().getGadgetId())
.setTrifleItem(this.getItem().toProto())
.setBornType(GadgetBornType.GADGET_BORN_TYPE_IN_AIR)
.setAuthorityPeerId(this.getWorld().getHostPeerId())
.setIsEnableInteract(true);
entityInfo.setGadget(gadgetInfo);
return entityInfo.build();
}
}

View File

@@ -1,22 +1,22 @@
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
public abstract class GadgetContent {
private final EntityGadget gadget;
public GadgetContent(EntityGadget gadget) {
this.gadget = gadget;
}
public EntityGadget getGadget() {
return gadget;
}
public abstract boolean onInteract(Player player, GadgetInteractReq req);
public abstract void onBuildProto(SceneGadgetInfo.Builder gadgetInfo);
}
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
public abstract class GadgetContent {
private final EntityGadget gadget;
public GadgetContent(EntityGadget gadget) {
this.gadget = gadget;
}
public EntityGadget getGadget() {
return gadget;
}
public abstract boolean onInteract(Player player, GadgetInteractReq req);
public abstract void onBuildProto(SceneGadgetInfo.Builder gadgetInfo);
}

View File

@@ -1,20 +1,20 @@
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass;
public class GadgetObject extends GadgetContent {
public GadgetObject(EntityGadget gadget) {
super(gadget);
}
@Override
public boolean onInteract(Player player, GadgetInteractReqOuterClass.GadgetInteractReq req) {
return false;
}
@Override
public void onBuildProto(SceneGadgetInfoOuterClass.SceneGadgetInfo.Builder gadgetInfo) {}
}
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass;
public class GadgetObject extends GadgetContent {
public GadgetObject(EntityGadget gadget) {
super(gadget);
}
@Override
public boolean onInteract(Player player, GadgetInteractReqOuterClass.GadgetInteractReq req) {
return false;
}
@Override
public void onBuildProto(SceneGadgetInfoOuterClass.SceneGadgetInfo.Builder gadgetInfo) {}
}

View File

@@ -1,50 +1,50 @@
package emu.grasscutter.game.entity.gadget.chest;
import emu.grasscutter.game.entity.gadget.GadgetChest;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.ChestReward;
import java.util.Random;
public class NormalChestInteractHandler implements ChestInteractHandler {
private final ChestReward chestReward;
public NormalChestInteractHandler(ChestReward rewardData) {
this.chestReward = rewardData;
}
@Override
public boolean isTwoStep() {
return false;
}
@Override
public boolean onInteract(GadgetChest chest, Player player) {
player.earnExp(chestReward.getAdvExp());
player.getInventory().addItem(201, chestReward.getResin());
var mora = chestReward.getMora() * (1 + (player.getWorldLevel() - 1) * 0.5);
player.getInventory().addItem(202, (int) mora);
for (int i = 0; i < chestReward.getContent().size(); i++) {
chest
.getGadget()
.getScene()
.addItemEntity(
chestReward.getContent().get(i).getItemId(),
chestReward.getContent().get(i).getCount(),
chest.getGadget());
}
var random = new Random(System.currentTimeMillis());
for (int i = 0; i < chestReward.getRandomCount(); i++) {
var index = random.nextInt(chestReward.getRandomContent().size());
var item = chestReward.getRandomContent().get(index);
chest
.getGadget()
.getScene()
.addItemEntity(item.getItemId(), item.getCount(), chest.getGadget());
}
return true;
}
}
package emu.grasscutter.game.entity.gadget.chest;
import emu.grasscutter.game.entity.gadget.GadgetChest;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.ChestReward;
import java.util.Random;
public class NormalChestInteractHandler implements ChestInteractHandler {
private final ChestReward chestReward;
public NormalChestInteractHandler(ChestReward rewardData) {
this.chestReward = rewardData;
}
@Override
public boolean isTwoStep() {
return false;
}
@Override
public boolean onInteract(GadgetChest chest, Player player) {
player.earnExp(chestReward.getAdvExp());
player.getInventory().addItem(201, chestReward.getResin());
var mora = chestReward.getMora() * (1 + (player.getWorldLevel() - 1) * 0.5);
player.getInventory().addItem(202, (int) mora);
for (int i = 0; i < chestReward.getContent().size(); i++) {
chest
.getGadget()
.getScene()
.addItemEntity(
chestReward.getContent().get(i).getItemId(),
chestReward.getContent().get(i).getCount(),
chest.getGadget());
}
var random = new Random(System.currentTimeMillis());
for (int i = 0; i < chestReward.getRandomCount(); i++) {
var index = random.nextInt(chestReward.getRandomContent().size());
var item = chestReward.getRandomContent().get(index);
chest
.getGadget()
.getScene()
.addItemEntity(item.getItemId(), item.getCount(), chest.getGadget());
}
return true;
}
}

View File

@@ -1,7 +1,7 @@
package emu.grasscutter.game.entity.gadget.worktop;
import emu.grasscutter.game.entity.gadget.GadgetWorktop;
public interface WorktopWorktopOptionHandler {
boolean onSelectWorktopOption(GadgetWorktop gadgetWorktop, int option);
}
package emu.grasscutter.game.entity.gadget.worktop;
import emu.grasscutter.game.entity.gadget.GadgetWorktop;
public interface WorktopWorktopOptionHandler {
boolean onSelectWorktopOption(GadgetWorktop gadgetWorktop, int option);
}

View File

@@ -1,25 +1,25 @@
package emu.grasscutter.game.expedition;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.AvatarExpeditionInfoOuterClass.AvatarExpeditionInfo;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
public class ExpeditionInfo {
private int state;
private int expId;
private int hourTime;
private int startTime;
public AvatarExpeditionInfo toProto() {
return AvatarExpeditionInfo.newBuilder()
.setStateValue(this.getState())
.setExpId(this.getExpId())
.setHourTime(this.getHourTime())
.setStartTime(this.getStartTime())
.build();
}
}
package emu.grasscutter.game.expedition;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.AvatarExpeditionInfoOuterClass.AvatarExpeditionInfo;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter
@Setter
public class ExpeditionInfo {
private int state;
private int expId;
private int hourTime;
private int startTime;
public AvatarExpeditionInfo toProto() {
return AvatarExpeditionInfo.newBuilder()
.setStateValue(this.getState())
.setExpId(this.getExpId())
.setHourTime(this.getHourTime())
.setStartTime(this.getStartTime())
.build();
}
}

View File

@@ -1,15 +1,15 @@
package emu.grasscutter.game.expedition;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.utils.Utils;
import lombok.Getter;
public class ExpeditionRewardData {
@Getter private int itemId;
@Getter private int minCount;
@Getter private int maxCount;
public GameItem getReward() {
return new GameItem(itemId, Utils.randomRange(minCount, maxCount));
}
}
package emu.grasscutter.game.expedition;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.utils.Utils;
import lombok.Getter;
public class ExpeditionRewardData {
@Getter private int itemId;
@Getter private int minCount;
@Getter private int maxCount;
public GameItem getReward() {
return new GameItem(itemId, Utils.randomRange(minCount, maxCount));
}
}

View File

@@ -1,19 +1,19 @@
package emu.grasscutter.game.expedition;
import emu.grasscutter.game.inventory.GameItem;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
public class ExpeditionRewardDataList {
@Getter private int hourTime;
@Getter private List<ExpeditionRewardData> expeditionRewardData;
public List<GameItem> getRewards() {
List<GameItem> rewards = new ArrayList<>();
if (expeditionRewardData != null) {
expeditionRewardData.forEach(data -> rewards.add(data.getReward()));
}
return rewards;
}
}
package emu.grasscutter.game.expedition;
import emu.grasscutter.game.inventory.GameItem;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
public class ExpeditionRewardDataList {
@Getter private int hourTime;
@Getter private List<ExpeditionRewardData> expeditionRewardData;
public List<GameItem> getRewards() {
List<GameItem> rewards = new ArrayList<>();
if (expeditionRewardData != null) {
expeditionRewardData.forEach(data -> rewards.add(data.getReward()));
}
return rewards;
}
}

View File

@@ -1,9 +1,9 @@
package emu.grasscutter.game.expedition;
import java.util.List;
import lombok.Getter;
public class ExpeditionRewardInfo {
@Getter private int expId;
@Getter private List<ExpeditionRewardDataList> expeditionRewardDataList;
}
package emu.grasscutter.game.expedition;
import java.util.List;
import lombok.Getter;
public class ExpeditionRewardInfo {
@Getter private int expId;
@Getter private List<ExpeditionRewardDataList> expeditionRewardDataList;
}

View File

@@ -1,42 +1,42 @@
package emu.grasscutter.game.expedition;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.List;
public class ExpeditionSystem extends BaseGameSystem {
private final Int2ObjectMap<List<ExpeditionRewardDataList>> expeditionRewardData;
public ExpeditionSystem(GameServer server) {
super(server);
this.expeditionRewardData = new Int2ObjectOpenHashMap<>();
this.load();
}
public Int2ObjectMap<List<ExpeditionRewardDataList>> getExpeditionRewardDataList() {
return expeditionRewardData;
}
public synchronized void load() {
getExpeditionRewardDataList().clear();
try {
List<ExpeditionRewardInfo> banners =
DataLoader.loadList("ExpeditionReward.json", ExpeditionRewardInfo.class);
if (banners.size() > 0) {
for (ExpeditionRewardInfo di : banners) {
getExpeditionRewardDataList().put(di.getExpId(), di.getExpeditionRewardDataList());
}
Grasscutter.getLogger().debug("Expedition reward successfully loaded.");
} else {
Grasscutter.getLogger()
.error("Unable to load expedition reward. Expedition reward size is 0.");
}
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load expedition reward.", e);
}
}
}
package emu.grasscutter.game.expedition;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.List;
public class ExpeditionSystem extends BaseGameSystem {
private final Int2ObjectMap<List<ExpeditionRewardDataList>> expeditionRewardData;
public ExpeditionSystem(GameServer server) {
super(server);
this.expeditionRewardData = new Int2ObjectOpenHashMap<>();
this.load();
}
public Int2ObjectMap<List<ExpeditionRewardDataList>> getExpeditionRewardDataList() {
return expeditionRewardData;
}
public synchronized void load() {
getExpeditionRewardDataList().clear();
try {
List<ExpeditionRewardInfo> banners =
DataLoader.loadList("ExpeditionReward.json", ExpeditionRewardInfo.class);
if (banners.size() > 0) {
for (ExpeditionRewardInfo di : banners) {
getExpeditionRewardDataList().put(di.getExpId(), di.getExpeditionRewardDataList());
}
Grasscutter.getLogger().debug("Expedition reward successfully loaded.");
} else {
Grasscutter.getLogger()
.error("Unable to load expedition reward. Expedition reward size is 0.");
}
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load expedition reward.", e);
}
}
}

View File

@@ -1,254 +1,254 @@
package emu.grasscutter.game.friends;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.DealAddFriendResultTypeOuterClass.DealAddFriendResultType;
import emu.grasscutter.server.packet.send.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.List;
public class FriendsList extends BasePlayerManager {
private final Int2ObjectMap<Friendship> friends;
private final Int2ObjectMap<Friendship> pendingFriends;
private boolean loaded = false;
public FriendsList(Player player) {
super(player);
this.friends = new Int2ObjectOpenHashMap<Friendship>();
this.pendingFriends = new Int2ObjectOpenHashMap<Friendship>();
}
public boolean hasLoaded() {
return loaded;
}
public synchronized Int2ObjectMap<Friendship> getFriends() {
return friends;
}
public synchronized Int2ObjectMap<Friendship> getPendingFriends() {
return this.pendingFriends;
}
public synchronized boolean isFriendsWith(int uid) {
return this.getFriends().containsKey(uid);
}
private synchronized Friendship getFriendshipById(int id) {
Friendship friendship = this.getFriends().get(id);
if (friendship == null) {
friendship = this.getPendingFriendById(id);
}
return friendship;
}
private synchronized Friendship getFriendById(int id) {
return this.getFriends().get(id);
}
private synchronized Friendship getPendingFriendById(int id) {
return this.getPendingFriends().get(id);
}
public void addFriend(Friendship friendship) {
getFriends().put(friendship.getFriendId(), friendship);
}
public void addPendingFriend(Friendship friendship) {
getPendingFriends().put(friendship.getFriendId(), friendship);
}
public synchronized void handleFriendRequest(int targetUid, DealAddFriendResultType result) {
// Check if player has sent friend request
Friendship myFriendship = this.getPendingFriendById(targetUid);
if (myFriendship == null) {
return;
}
// Make sure asker cant do anything
if (myFriendship.getAskerId() == this.getPlayer().getUid()) {
return;
}
Player target = getPlayer().getSession().getServer().getPlayerByUid(targetUid, true);
if (target == null) {
return; // Should never happen
}
// Get target's friendship
Friendship theirFriendship = null;
if (target.isOnline()) {
theirFriendship = target.getFriendsList().getPendingFriendById(this.getPlayer().getUid());
} else {
theirFriendship = DatabaseHelper.getReverseFriendship(myFriendship);
}
if (theirFriendship == null) {
// They dont have us on their friends list anymore, rip
this.getPendingFriends().remove(myFriendship.getOwnerId());
myFriendship.delete();
return;
}
// Handle
if (result == DealAddFriendResultType.DEAL_ADD_FRIEND_RESULT_TYPE_ACCEPT) { // Request accepted
myFriendship.setIsFriend(true);
theirFriendship.setIsFriend(true);
this.getPendingFriends().remove(myFriendship.getOwnerId());
this.addFriend(myFriendship);
if (target.isOnline()) {
target.getFriendsList().getPendingFriends().remove(this.getPlayer().getUid());
target.getFriendsList().addFriend(theirFriendship);
}
myFriendship.save();
theirFriendship.save();
} else { // Request declined
// Delete from my pending friends
this.getPendingFriends().remove(myFriendship.getOwnerId());
myFriendship.delete();
// Delete from target uid
if (target.isOnline()) {
theirFriendship = target.getFriendsList().getPendingFriendById(this.getPlayer().getUid());
}
theirFriendship.delete();
}
// Packet
this.getPlayer().sendPacket(new PacketDealAddFriendRsp(targetUid, result));
}
public synchronized void deleteFriend(int targetUid) {
Friendship myFriendship = this.getFriendById(targetUid);
if (myFriendship == null) {
return;
}
this.getFriends().remove(targetUid);
myFriendship.delete();
Friendship theirFriendship = null;
Player friend = myFriendship.getFriendProfile().getPlayer();
if (friend != null) {
// Friend online
theirFriendship = friend.getFriendsList().getFriendById(this.getPlayer().getUid());
if (theirFriendship != null) {
friend.getFriendsList().getFriends().remove(theirFriendship.getFriendId());
theirFriendship.delete();
friend.sendPacket(new PacketDeleteFriendNotify(theirFriendship.getFriendId()));
}
} else {
// Friend offline
theirFriendship = DatabaseHelper.getReverseFriendship(myFriendship);
if (theirFriendship != null) {
theirFriendship.delete();
}
}
// Packet
this.getPlayer().sendPacket(new PacketDeleteFriendRsp(targetUid));
}
public synchronized void sendFriendRequest(int targetUid) {
Player target = getPlayer().getSession().getServer().getPlayerByUid(targetUid, true);
if (target == null || target == this.getPlayer()) {
return;
}
// Check if friend already exists
if (this.getPendingFriends().containsKey(targetUid)
|| this.getFriends().containsKey(targetUid)) {
return;
}
// Create friendships
Friendship myFriendship = new Friendship(getPlayer(), target, getPlayer());
Friendship theirFriendship = new Friendship(target, getPlayer(), getPlayer());
// Add pending lists
this.addPendingFriend(myFriendship);
if (target.isOnline() && target.getFriendsList().hasLoaded()) {
target.getFriendsList().addPendingFriend(theirFriendship);
target.sendPacket(new PacketAskAddFriendNotify(theirFriendship));
}
// Save
myFriendship.save();
theirFriendship.save();
// Packets
this.getPlayer().sendPacket(new PacketAskAddFriendRsp(targetUid));
}
/** Gets total amount of potential friends */
public int getFullFriendCount() {
return this.getPendingFriends().size() + this.getFriends().size();
}
public synchronized void loadFromDatabase() {
if (this.hasLoaded()) {
return;
}
// Get friendships from the db
List<Friendship> friendships = DatabaseHelper.getFriends(player);
friendships.forEach(this::loadFriendFromDatabase);
// Set loaded flag
this.loaded = true;
}
private void loadFriendFromDatabase(Friendship friendship) {
// Set friendship owner
friendship.setOwner(getPlayer());
// Check if friend is online
Player friend =
getPlayer().getSession().getServer().getPlayerByUid(friendship.getFriendProfile().getUid());
if (friend != null) {
// Set friend to online mode
friendship.setFriendProfile(friend);
// Update our status on friend's client if theyre online
if (friend.getFriendsList().hasLoaded()) {
Friendship theirFriendship =
friend.getFriendsList().getFriendshipById(getPlayer().getUid());
if (theirFriendship != null) {
// Update friend profile
theirFriendship.setFriendProfile(getPlayer());
} else {
// They dont have us on their friends list anymore, rip
friendship.delete();
return;
}
}
}
// Finally, load to our friends list
if (friendship.isFriend()) {
getFriends().put(friendship.getFriendId(), friendship);
} else {
getPendingFriends().put(friendship.getFriendId(), friendship);
// TODO - Hacky fix to force client to see a notification for a friendship
if (getPendingFriends().size() == 1) {
getPlayer().getSession().send(new PacketAskAddFriendNotify(friendship));
}
}
}
public void save() {
// Update all our friends
List<Friendship> friendships = DatabaseHelper.getReverseFriends(getPlayer());
for (Friendship friend : friendships) {
friend.setFriendProfile(this.getPlayer());
friend.save();
}
}
}
package emu.grasscutter.game.friends;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.DealAddFriendResultTypeOuterClass.DealAddFriendResultType;
import emu.grasscutter.server.packet.send.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.List;
public class FriendsList extends BasePlayerManager {
private final Int2ObjectMap<Friendship> friends;
private final Int2ObjectMap<Friendship> pendingFriends;
private boolean loaded = false;
public FriendsList(Player player) {
super(player);
this.friends = new Int2ObjectOpenHashMap<Friendship>();
this.pendingFriends = new Int2ObjectOpenHashMap<Friendship>();
}
public boolean hasLoaded() {
return loaded;
}
public synchronized Int2ObjectMap<Friendship> getFriends() {
return friends;
}
public synchronized Int2ObjectMap<Friendship> getPendingFriends() {
return this.pendingFriends;
}
public synchronized boolean isFriendsWith(int uid) {
return this.getFriends().containsKey(uid);
}
private synchronized Friendship getFriendshipById(int id) {
Friendship friendship = this.getFriends().get(id);
if (friendship == null) {
friendship = this.getPendingFriendById(id);
}
return friendship;
}
private synchronized Friendship getFriendById(int id) {
return this.getFriends().get(id);
}
private synchronized Friendship getPendingFriendById(int id) {
return this.getPendingFriends().get(id);
}
public void addFriend(Friendship friendship) {
getFriends().put(friendship.getFriendId(), friendship);
}
public void addPendingFriend(Friendship friendship) {
getPendingFriends().put(friendship.getFriendId(), friendship);
}
public synchronized void handleFriendRequest(int targetUid, DealAddFriendResultType result) {
// Check if player has sent friend request
Friendship myFriendship = this.getPendingFriendById(targetUid);
if (myFriendship == null) {
return;
}
// Make sure asker cant do anything
if (myFriendship.getAskerId() == this.getPlayer().getUid()) {
return;
}
Player target = getPlayer().getSession().getServer().getPlayerByUid(targetUid, true);
if (target == null) {
return; // Should never happen
}
// Get target's friendship
Friendship theirFriendship = null;
if (target.isOnline()) {
theirFriendship = target.getFriendsList().getPendingFriendById(this.getPlayer().getUid());
} else {
theirFriendship = DatabaseHelper.getReverseFriendship(myFriendship);
}
if (theirFriendship == null) {
// They dont have us on their friends list anymore, rip
this.getPendingFriends().remove(myFriendship.getOwnerId());
myFriendship.delete();
return;
}
// Handle
if (result == DealAddFriendResultType.DEAL_ADD_FRIEND_RESULT_TYPE_ACCEPT) { // Request accepted
myFriendship.setIsFriend(true);
theirFriendship.setIsFriend(true);
this.getPendingFriends().remove(myFriendship.getOwnerId());
this.addFriend(myFriendship);
if (target.isOnline()) {
target.getFriendsList().getPendingFriends().remove(this.getPlayer().getUid());
target.getFriendsList().addFriend(theirFriendship);
}
myFriendship.save();
theirFriendship.save();
} else { // Request declined
// Delete from my pending friends
this.getPendingFriends().remove(myFriendship.getOwnerId());
myFriendship.delete();
// Delete from target uid
if (target.isOnline()) {
theirFriendship = target.getFriendsList().getPendingFriendById(this.getPlayer().getUid());
}
theirFriendship.delete();
}
// Packet
this.getPlayer().sendPacket(new PacketDealAddFriendRsp(targetUid, result));
}
public synchronized void deleteFriend(int targetUid) {
Friendship myFriendship = this.getFriendById(targetUid);
if (myFriendship == null) {
return;
}
this.getFriends().remove(targetUid);
myFriendship.delete();
Friendship theirFriendship = null;
Player friend = myFriendship.getFriendProfile().getPlayer();
if (friend != null) {
// Friend online
theirFriendship = friend.getFriendsList().getFriendById(this.getPlayer().getUid());
if (theirFriendship != null) {
friend.getFriendsList().getFriends().remove(theirFriendship.getFriendId());
theirFriendship.delete();
friend.sendPacket(new PacketDeleteFriendNotify(theirFriendship.getFriendId()));
}
} else {
// Friend offline
theirFriendship = DatabaseHelper.getReverseFriendship(myFriendship);
if (theirFriendship != null) {
theirFriendship.delete();
}
}
// Packet
this.getPlayer().sendPacket(new PacketDeleteFriendRsp(targetUid));
}
public synchronized void sendFriendRequest(int targetUid) {
Player target = getPlayer().getSession().getServer().getPlayerByUid(targetUid, true);
if (target == null || target == this.getPlayer()) {
return;
}
// Check if friend already exists
if (this.getPendingFriends().containsKey(targetUid)
|| this.getFriends().containsKey(targetUid)) {
return;
}
// Create friendships
Friendship myFriendship = new Friendship(getPlayer(), target, getPlayer());
Friendship theirFriendship = new Friendship(target, getPlayer(), getPlayer());
// Add pending lists
this.addPendingFriend(myFriendship);
if (target.isOnline() && target.getFriendsList().hasLoaded()) {
target.getFriendsList().addPendingFriend(theirFriendship);
target.sendPacket(new PacketAskAddFriendNotify(theirFriendship));
}
// Save
myFriendship.save();
theirFriendship.save();
// Packets
this.getPlayer().sendPacket(new PacketAskAddFriendRsp(targetUid));
}
/** Gets total amount of potential friends */
public int getFullFriendCount() {
return this.getPendingFriends().size() + this.getFriends().size();
}
public synchronized void loadFromDatabase() {
if (this.hasLoaded()) {
return;
}
// Get friendships from the db
List<Friendship> friendships = DatabaseHelper.getFriends(player);
friendships.forEach(this::loadFriendFromDatabase);
// Set loaded flag
this.loaded = true;
}
private void loadFriendFromDatabase(Friendship friendship) {
// Set friendship owner
friendship.setOwner(getPlayer());
// Check if friend is online
Player friend =
getPlayer().getSession().getServer().getPlayerByUid(friendship.getFriendProfile().getUid());
if (friend != null) {
// Set friend to online mode
friendship.setFriendProfile(friend);
// Update our status on friend's client if theyre online
if (friend.getFriendsList().hasLoaded()) {
Friendship theirFriendship =
friend.getFriendsList().getFriendshipById(getPlayer().getUid());
if (theirFriendship != null) {
// Update friend profile
theirFriendship.setFriendProfile(getPlayer());
} else {
// They dont have us on their friends list anymore, rip
friendship.delete();
return;
}
}
}
// Finally, load to our friends list
if (friendship.isFriend()) {
getFriends().put(friendship.getFriendId(), friendship);
} else {
getPendingFriends().put(friendship.getFriendId(), friendship);
// TODO - Hacky fix to force client to see a notification for a friendship
if (getPendingFriends().size() == 1) {
getPlayer().getSession().send(new PacketAskAddFriendNotify(friendship));
}
}
}
public void save() {
// Update all our friends
List<Friendship> friendships = DatabaseHelper.getReverseFriends(getPlayer());
for (Friendship friend : friendships) {
friend.setFriendProfile(this.getPlayer());
friend.save();
}
}
}

View File

@@ -1,116 +1,116 @@
package emu.grasscutter.game.friends;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.Transient;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.FriendBriefOuterClass.FriendBrief;
import emu.grasscutter.net.proto.FriendOnlineStateOuterClass.FriendOnlineState;
import emu.grasscutter.net.proto.PlatformTypeOuterClass;
import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture;
import org.bson.types.ObjectId;
@Entity(value = "friendships", useDiscriminator = false)
public class Friendship {
@Id private ObjectId id;
@Transient private Player owner;
@Indexed private int ownerId;
@Indexed private int friendId;
private boolean isFriend;
private int askerId;
private PlayerProfile profile;
@Deprecated // Morphia use only
public Friendship() {}
public Friendship(Player owner, Player friend, Player asker) {
this.setOwner(owner);
this.ownerId = owner.getUid();
this.friendId = friend.getUid();
this.profile = friend.getProfile();
this.askerId = asker.getUid();
}
public Player getOwner() {
return owner;
}
public void setOwner(Player owner) {
this.owner = owner;
}
public boolean isFriend() {
return isFriend;
}
public void setIsFriend(boolean b) {
this.isFriend = b;
}
public int getOwnerId() {
return ownerId;
}
public int getFriendId() {
return friendId;
}
public int getAskerId() {
return askerId;
}
public void setAskerId(int askerId) {
this.askerId = askerId;
}
public PlayerProfile getFriendProfile() {
return profile;
}
public void setFriendProfile(Player character) {
if (character == null || this.friendId != character.getUid()) return;
this.profile = character.getProfile();
}
public boolean isOnline() {
return getFriendProfile().getPlayer() != null;
}
public void save() {
DatabaseHelper.saveFriendship(this);
}
public void delete() {
DatabaseHelper.deleteFriendship(this);
}
public FriendBrief toProto() {
FriendBrief proto =
FriendBrief.newBuilder()
.setUid(getFriendProfile().getUid())
.setNickname(getFriendProfile().getName())
.setLevel(getFriendProfile().getPlayerLevel())
.setProfilePicture(
ProfilePicture.newBuilder().setAvatarId(getFriendProfile().getAvatarId()))
.setWorldLevel(getFriendProfile().getWorldLevel())
.setSignature(getFriendProfile().getSignature())
.setOnlineState(
getFriendProfile().isOnline()
? FriendOnlineState.FRIEND_ONLINE_STATE_ONLINE
: FriendOnlineState.FRIEND_ONLINE_STATE_DISCONNECT)
.setIsMpModeAvailable(true)
.setLastActiveTime(getFriendProfile().getLastActiveTime())
.setNameCardId(getFriendProfile().getNameCard())
.setParam(getFriendProfile().getDaysSinceLogin())
.setIsGameSource(true)
.setPlatformType(PlatformTypeOuterClass.PlatformType.PLATFORM_TYPE_PC)
.build();
return proto;
}
}
package emu.grasscutter.game.friends;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.Transient;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.FriendBriefOuterClass.FriendBrief;
import emu.grasscutter.net.proto.FriendOnlineStateOuterClass.FriendOnlineState;
import emu.grasscutter.net.proto.PlatformTypeOuterClass;
import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture;
import org.bson.types.ObjectId;
@Entity(value = "friendships", useDiscriminator = false)
public class Friendship {
@Id private ObjectId id;
@Transient private Player owner;
@Indexed private int ownerId;
@Indexed private int friendId;
private boolean isFriend;
private int askerId;
private PlayerProfile profile;
@Deprecated // Morphia use only
public Friendship() {}
public Friendship(Player owner, Player friend, Player asker) {
this.setOwner(owner);
this.ownerId = owner.getUid();
this.friendId = friend.getUid();
this.profile = friend.getProfile();
this.askerId = asker.getUid();
}
public Player getOwner() {
return owner;
}
public void setOwner(Player owner) {
this.owner = owner;
}
public boolean isFriend() {
return isFriend;
}
public void setIsFriend(boolean b) {
this.isFriend = b;
}
public int getOwnerId() {
return ownerId;
}
public int getFriendId() {
return friendId;
}
public int getAskerId() {
return askerId;
}
public void setAskerId(int askerId) {
this.askerId = askerId;
}
public PlayerProfile getFriendProfile() {
return profile;
}
public void setFriendProfile(Player character) {
if (character == null || this.friendId != character.getUid()) return;
this.profile = character.getProfile();
}
public boolean isOnline() {
return getFriendProfile().getPlayer() != null;
}
public void save() {
DatabaseHelper.saveFriendship(this);
}
public void delete() {
DatabaseHelper.deleteFriendship(this);
}
public FriendBrief toProto() {
FriendBrief proto =
FriendBrief.newBuilder()
.setUid(getFriendProfile().getUid())
.setNickname(getFriendProfile().getName())
.setLevel(getFriendProfile().getPlayerLevel())
.setProfilePicture(
ProfilePicture.newBuilder().setAvatarId(getFriendProfile().getAvatarId()))
.setWorldLevel(getFriendProfile().getWorldLevel())
.setSignature(getFriendProfile().getSignature())
.setOnlineState(
getFriendProfile().isOnline()
? FriendOnlineState.FRIEND_ONLINE_STATE_ONLINE
: FriendOnlineState.FRIEND_ONLINE_STATE_DISCONNECT)
.setIsMpModeAvailable(true)
.setLastActiveTime(getFriendProfile().getLastActiveTime())
.setNameCardId(getFriendProfile().getNameCard())
.setParam(getFriendProfile().getDaysSinceLogin())
.setIsGameSource(true)
.setPlatformType(PlatformTypeOuterClass.PlatformType.PLATFORM_TYPE_PC)
.build();
return proto;
}
}

View File

@@ -1,105 +1,105 @@
package emu.grasscutter.game.friends;
import dev.morphia.annotations.AlsoLoad;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.utils.Utils;
@Entity
public class PlayerProfile {
@Transient private Player player;
@AlsoLoad("id")
private int uid;
private int nameCard;
private int avatarId;
private String name;
private String signature;
private int achievements;
private int playerLevel;
private int worldLevel;
private int lastActiveTime;
@Deprecated // Morphia only
public PlayerProfile() {}
public PlayerProfile(Player player) {
this.uid = player.getUid();
this.syncWithCharacter(player);
}
public int getUid() {
return uid;
}
public Player getPlayer() {
return player;
}
public synchronized void setPlayer(Player player) {
this.player = player;
}
public String getName() {
return name;
}
public int getNameCard() {
return nameCard;
}
public int getAvatarId() {
return avatarId;
}
public String getSignature() {
return signature;
}
public int getAchievements() {
return achievements;
}
public int getPlayerLevel() {
return playerLevel;
}
public int getWorldLevel() {
return worldLevel;
}
public int getLastActiveTime() {
return lastActiveTime;
}
public void updateLastActiveTime() {
this.lastActiveTime = Utils.getCurrentSeconds();
}
public int getDaysSinceLogin() {
return (int) Math.floor((Utils.getCurrentSeconds() - getLastActiveTime()) / 86400.0);
}
public boolean isOnline() {
return this.getPlayer() != null;
}
public void syncWithCharacter(Player player) {
if (player == null) {
return;
}
this.uid = player.getUid();
this.name = player.getNickname();
this.avatarId = player.getHeadImage();
this.signature = player.getSignature();
this.nameCard = player.getNameCardId();
this.playerLevel = player.getLevel();
this.worldLevel = player.getWorldLevel();
// this.achievements = 0;
this.updateLastActiveTime();
}
}
package emu.grasscutter.game.friends;
import dev.morphia.annotations.AlsoLoad;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.utils.Utils;
@Entity
public class PlayerProfile {
@Transient private Player player;
@AlsoLoad("id")
private int uid;
private int nameCard;
private int avatarId;
private String name;
private String signature;
private int achievements;
private int playerLevel;
private int worldLevel;
private int lastActiveTime;
@Deprecated // Morphia only
public PlayerProfile() {}
public PlayerProfile(Player player) {
this.uid = player.getUid();
this.syncWithCharacter(player);
}
public int getUid() {
return uid;
}
public Player getPlayer() {
return player;
}
public synchronized void setPlayer(Player player) {
this.player = player;
}
public String getName() {
return name;
}
public int getNameCard() {
return nameCard;
}
public int getAvatarId() {
return avatarId;
}
public String getSignature() {
return signature;
}
public int getAchievements() {
return achievements;
}
public int getPlayerLevel() {
return playerLevel;
}
public int getWorldLevel() {
return worldLevel;
}
public int getLastActiveTime() {
return lastActiveTime;
}
public void updateLastActiveTime() {
this.lastActiveTime = Utils.getCurrentSeconds();
}
public int getDaysSinceLogin() {
return (int) Math.floor((Utils.getCurrentSeconds() - getLastActiveTime()) / 86400.0);
}
public boolean isOnline() {
return this.getPlayer() != null;
}
public void syncWithCharacter(Player player) {
if (player == null) {
return;
}
this.uid = player.getUid();
this.name = player.getNickname();
this.avatarId = player.getHeadImage();
this.signature = player.getSignature();
this.nameCard = player.getNameCardId();
this.playerLevel = player.getLevel();
this.worldLevel = player.getWorldLevel();
// this.achievements = 0;
this.updateLastActiveTime();
}
}

View File

@@ -1,75 +1,75 @@
package emu.grasscutter.game.gacha;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import java.util.Date;
import org.bson.types.ObjectId;
@Entity(value = "gachas", useDiscriminator = false)
public class GachaRecord {
@Id private ObjectId id;
@Indexed private int ownerId;
private Date transactionDate;
private int itemID;
@Indexed private int gachaType;
public GachaRecord() {}
public GachaRecord(int itemId, int ownerId, int gachaType) {
this.transactionDate = new Date();
this.itemID = itemId;
this.ownerId = ownerId;
this.gachaType = gachaType;
}
public int getOwnerId() {
return ownerId;
}
public void setOwnerId(int ownerId) {
this.ownerId = ownerId;
}
public int getGachaType() {
return gachaType;
}
public void setGachaType(int type) {
this.gachaType = type;
}
public Date getTransactionDate() {
return transactionDate;
}
public void setTransactionDate(Date transactionDate) {
this.transactionDate = transactionDate;
}
public int getItemID() {
return itemID;
}
public void setItemID(int itemID) {
this.itemID = itemID;
}
public ObjectId getId() {
return id;
}
public void setId(ObjectId id) {
this.id = id;
}
public String toString() {
return toJsonString();
}
public String toJsonString() {
return "{\"time\": " + this.transactionDate.getTime() + ",\"item\":" + this.itemID + "}";
}
}
package emu.grasscutter.game.gacha;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import java.util.Date;
import org.bson.types.ObjectId;
@Entity(value = "gachas", useDiscriminator = false)
public class GachaRecord {
@Id private ObjectId id;
@Indexed private int ownerId;
private Date transactionDate;
private int itemID;
@Indexed private int gachaType;
public GachaRecord() {}
public GachaRecord(int itemId, int ownerId, int gachaType) {
this.transactionDate = new Date();
this.itemID = itemId;
this.ownerId = ownerId;
this.gachaType = gachaType;
}
public int getOwnerId() {
return ownerId;
}
public void setOwnerId(int ownerId) {
this.ownerId = ownerId;
}
public int getGachaType() {
return gachaType;
}
public void setGachaType(int type) {
this.gachaType = type;
}
public Date getTransactionDate() {
return transactionDate;
}
public void setTransactionDate(Date transactionDate) {
this.transactionDate = transactionDate;
}
public int getItemID() {
return itemID;
}
public void setItemID(int itemID) {
this.itemID = itemID;
}
public ObjectId getId() {
return id;
}
public void setId(ObjectId id) {
this.id = id;
}
public String toString() {
return toJsonString();
}
public String toJsonString() {
return "{\"time\": " + this.transactionDate.getTime() + ",\"item\":" + this.itemID + "}";
}
}

View File

@@ -1,491 +1,491 @@
package emu.grasscutter.game.gacha;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
import com.sun.nio.file.SensitivityWatchEventModifier;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.gacha.GachaBanner.BannerType;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.systems.InventorySystem;
import emu.grasscutter.net.proto.GachaItemOuterClass.GachaItem;
import emu.grasscutter.net.proto.GachaTransferItemOuterClass.GachaTransferItem;
import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.game.GameServerTickEvent;
import emu.grasscutter.server.packet.send.PacketDoGachaRsp;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import org.greenrobot.eventbus.Subscribe;
public class GachaSystem extends BaseGameSystem {
private static final int starglitterId = 221;
private static final int stardustId = 222;
private final Int2ObjectMap<GachaBanner> gachaBanners;
private WatchService watchService;
public GachaSystem(GameServer server) {
super(server);
this.gachaBanners = new Int2ObjectOpenHashMap<>();
this.load();
this.startWatcher(server);
}
public Int2ObjectMap<GachaBanner> getGachaBanners() {
return gachaBanners;
}
public int randomRange(int min, int max) { // Both are inclusive
return ThreadLocalRandom.current().nextInt(max - min + 1) + min;
}
public int getRandom(int[] array) {
return array[randomRange(0, array.length - 1)];
}
public synchronized void load() {
getGachaBanners().clear();
int autoScheduleId = 1000;
int autoSortId = 9000;
try {
List<GachaBanner> banners = DataLoader.loadTableToList("Banners", GachaBanner.class);
if (banners.size() > 0) {
for (GachaBanner banner : banners) {
banner.onLoad();
if (banner.isDeprecated()) {
Grasscutter.getLogger()
.error(
"A Banner has not been loaded because it contains one or more deprecated fields. Remove the fields mentioned above and reload.");
} else if (banner.isDisabled()) {
Grasscutter.getLogger().debug("A Banner has not been loaded because it is disabled.");
} else {
if (banner.scheduleId < 0) banner.scheduleId = autoScheduleId++;
if (banner.sortId < 0) banner.sortId = autoSortId--;
getGachaBanners().put(banner.scheduleId, banner);
}
}
Grasscutter.getLogger().debug("Banners successfully loaded.");
} else {
Grasscutter.getLogger().error("Unable to load banners. Banners size is 0.");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private synchronized int[] removeC6FromPool(int[] itemPool, Player player) {
IntList temp = new IntArrayList();
for (int itemId : itemPool) {
if (InventorySystem.checkPlayerAvatarConstellationLevel(player, itemId) < 6) {
temp.add(itemId);
}
}
return temp.toIntArray();
}
private synchronized int drawRoulette(int[] weights, int cutoff) {
// This follows the logic laid out in issue #183
// Simple weighted selection with an upper bound for the roll that cuts off trailing entries
// All weights must be >= 0
int total = 0;
for (int weight : weights) {
if (weight < 0) {
throw new IllegalArgumentException("Weights must be non-negative!");
}
total += weight;
}
int roll = ThreadLocalRandom.current().nextInt((total < cutoff) ? total : cutoff);
int subTotal = 0;
for (int i = 0; i < weights.length; i++) {
subTotal += weights[i];
if (roll < subTotal) {
return i;
}
}
// throw new IllegalStateException();
return 0; // This should only be reachable if total==0
}
private synchronized int doFallbackRarePull(
int[] fallback1,
int[] fallback2,
int rarity,
GachaBanner banner,
PlayerGachaBannerInfo gachaInfo) {
if (fallback1.length < 1) {
if (fallback2.length < 1) {
return getRandom(
(rarity == 5)
? GachaBanner.DEFAULT_FALLBACK_ITEMS_5_POOL_2
: GachaBanner.DEFAULT_FALLBACK_ITEMS_4_POOL_2);
} else {
return getRandom(fallback2);
}
} else if (fallback2.length < 1) {
return getRandom(fallback1);
} else { // Both pools are possible, use the pool balancer
int pityPool1 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 1));
int pityPool2 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 2));
int chosenPool =
switch ((pityPool1 >= pityPool2)
? 1
: 0) { // Larger weight must come first for the hard cutoff to function correctly
case 1 -> 1 + drawRoulette(new int[] {pityPool1, pityPool2}, 10000);
default -> 2 - drawRoulette(new int[] {pityPool2, pityPool1}, 10000);
};
return switch (chosenPool) {
case 1:
gachaInfo.setPityPool(rarity, 1, 0);
yield getRandom(fallback1);
default:
gachaInfo.setPityPool(rarity, 2, 0);
yield getRandom(fallback2);
};
}
}
private synchronized int doRarePull(
int[] featured,
int[] fallback1,
int[] fallback2,
int rarity,
GachaBanner banner,
PlayerGachaBannerInfo gachaInfo) {
int itemId = 0;
boolean epitomized =
(banner.hasEpitomized()) && (rarity == 5) && (gachaInfo.getWishItemId() != 0);
boolean pityEpitomized =
(gachaInfo.getFailedChosenItemPulls()
>= banner.getWishMaxProgress()); // Maximum fate points reached
boolean pityFeatured =
(gachaInfo.getFailedFeaturedItemPulls(rarity) >= 1); // Lost previous coinflip
boolean rollFeatured =
(this.randomRange(1, 100) <= banner.getEventChance(rarity)); // Won this coinflip
boolean pullFeatured = pityFeatured || rollFeatured;
if (epitomized && pityEpitomized) { // Auto pick item when epitomized points reached
gachaInfo.setFailedFeaturedItemPulls(
rarity, 0); // Epitomized item will always be a featured one
itemId = gachaInfo.getWishItemId();
} else {
if (pullFeatured && (featured.length > 0)) {
gachaInfo.setFailedFeaturedItemPulls(rarity, 0);
itemId = getRandom(featured);
} else {
gachaInfo.addFailedFeaturedItemPulls(
rarity,
1); // This could be moved into doFallbackRarePull but having it here makes it clearer
itemId = doFallbackRarePull(fallback1, fallback2, rarity, banner, gachaInfo);
}
}
if (epitomized) {
if (itemId == gachaInfo.getWishItemId()) { // Reset epitomized points when got wished item
gachaInfo.setFailedChosenItemPulls(0);
} else { // Add epitomized points if not get wished item
gachaInfo.addFailedChosenItemPulls(1);
}
}
return itemId;
}
private synchronized int doPull(
GachaBanner banner, PlayerGachaBannerInfo gachaInfo, BannerPools pools) {
// Pre-increment all pity pools (yes this makes all calculations assume 1-indexed pity)
gachaInfo.incPityAll();
int[] weights = {
banner.getWeight(5, gachaInfo.getPity5()), banner.getWeight(4, gachaInfo.getPity4()), 10000
};
int levelWon = 5 - drawRoulette(weights, 10000);
return switch (levelWon) {
case 5:
gachaInfo.setPity5(0);
yield doRarePull(
pools.rateUpItems5,
pools.fallbackItems5Pool1,
pools.fallbackItems5Pool2,
5,
banner,
gachaInfo);
case 4:
gachaInfo.setPity4(0);
yield doRarePull(
pools.rateUpItems4,
pools.fallbackItems4Pool1,
pools.fallbackItems4Pool2,
4,
banner,
gachaInfo);
default:
yield getRandom(banner.getFallbackItems3());
};
}
public synchronized void doPulls(Player player, int scheduleId, int times) {
// Sanity check
if (times != 10 && times != 1) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_INVALID_TIMES));
return;
}
Inventory inventory = player.getInventory();
if (inventory.getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times
> inventory.getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_ITEM_EXCEED_LIMIT));
return;
}
// Get banner
GachaBanner banner = this.getGachaBanners().get(scheduleId);
if (banner == null) {
player.sendPacket(new PacketDoGachaRsp());
return;
}
// Check against total limit
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(banner);
int gachaTimesLimit = banner.getGachaTimesLimit();
if (gachaTimesLimit != Integer.MAX_VALUE
&& (gachaInfo.getTotalPulls() + times) > gachaTimesLimit) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_TIMES_LIMIT));
return;
}
// Spend currency
ItemParamData cost = banner.getCost(times);
if (cost.getCount() > 0 && !inventory.payItem(cost)) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_COST_ITEM_NOT_ENOUGH));
return;
}
// Add to character
gachaInfo.addTotalPulls(times);
BannerPools pools = new BannerPools(banner);
List<GachaItem> list = new ArrayList<>();
int stardust = 0, starglitter = 0;
if (banner.isRemoveC6FromPool()) { // The ultimate form of pity (non-vanilla)
pools.rateUpItems4 = removeC6FromPool(pools.rateUpItems4, player);
pools.rateUpItems5 = removeC6FromPool(pools.rateUpItems5, player);
pools.fallbackItems4Pool1 = removeC6FromPool(pools.fallbackItems4Pool1, player);
pools.fallbackItems4Pool2 = removeC6FromPool(pools.fallbackItems4Pool2, player);
pools.fallbackItems5Pool1 = removeC6FromPool(pools.fallbackItems5Pool1, player);
pools.fallbackItems5Pool2 = removeC6FromPool(pools.fallbackItems5Pool2, player);
}
for (int i = 0; i < times; i++) {
// Roll
int itemId = doPull(banner, gachaInfo, pools);
ItemData itemData = GameData.getItemDataMap().get(itemId);
if (itemData == null) {
continue; // Maybe we should bail out if an item fails instead of rolling the rest?
}
// Write gacha record
GachaRecord gachaRecord = new GachaRecord(itemId, player.getUid(), banner.getGachaType());
DatabaseHelper.saveGachaRecord(gachaRecord);
// Create gacha item
GachaItem.Builder gachaItem = GachaItem.newBuilder();
int addStardust = 0, addStarglitter = 0;
boolean isTransferItem = false;
// Const check
int constellation = InventorySystem.checkPlayerAvatarConstellationLevel(player, itemId);
switch (constellation) {
case -2: // Is weapon
switch (itemData.getRankLevel()) {
case 5 -> addStarglitter = 10;
case 4 -> addStarglitter = 2;
default -> addStardust = 15;
}
break;
case -1: // New character
gachaItem.setIsGachaItemNew(true);
break;
default:
if (constellation >= 6) { // C6, give consolation starglitter
addStarglitter = (itemData.getRankLevel() == 5) ? 25 : 5;
} else { // C0-C5, give constellation item
if (banner.isRemoveC6FromPool()
&& constellation
== 5) { // New C6, remove it from the pools so we don't get C7 in a 10pull
pools.removeFromAllPools(new int[] {itemId});
}
addStarglitter = (itemData.getRankLevel() == 5) ? 10 : 2;
int constItemId =
itemId + 100; // This may not hold true for future characters. Examples of strictly
// correct constellation item lookup are elsewhere for now.
boolean haveConstItem =
inventory.getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId) == null;
gachaItem.addTransferItems(
GachaTransferItem.newBuilder()
.setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1))
.setIsTransferItemNew(haveConstItem));
// inventory.addItem(constItemId, 1); // This is now managed by the avatar card item
// itself
}
isTransferItem = true;
break;
}
// Create item
GameItem item = new GameItem(itemData);
gachaItem.setGachaItem(item.toItemParam());
inventory.addItem(item);
stardust += addStardust;
starglitter += addStarglitter;
if (addStardust > 0) {
gachaItem.addTokenItemList(
ItemParam.newBuilder().setItemId(stardustId).setCount(addStardust));
}
if (addStarglitter > 0) {
ItemParam starglitterParam =
ItemParam.newBuilder().setItemId(starglitterId).setCount(addStarglitter).build();
if (isTransferItem) {
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(starglitterParam));
}
gachaItem.addTokenItemList(starglitterParam);
}
list.add(gachaItem.build());
}
// Add stardust/starglitter
if (stardust > 0) {
inventory.addItem(stardustId, stardust);
}
if (starglitter > 0) {
inventory.addItem(starglitterId, starglitter);
}
// Packets
player.sendPacket(new PacketDoGachaRsp(banner, list, gachaInfo));
// Battle Pass trigger
player.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_GACHA_NUM, 0, times);
}
private synchronized void startWatcher(GameServer server) {
if (this.watchService == null) {
try {
this.watchService = FileSystems.getDefault().newWatchService();
FileUtils.getDataUserPath("")
.register(
watchService,
new WatchEvent.Kind[] {StandardWatchEventKinds.ENTRY_MODIFY},
SensitivityWatchEventModifier.HIGH);
} catch (Exception e) {
Grasscutter.getLogger()
.error(
"Unable to load the Gacha Manager Watch Service. If ServerOptions.watchGacha is true it will not auto-reload");
e.printStackTrace();
}
} else {
Grasscutter.getLogger().error("Cannot reinitialise watcher ");
}
}
@Subscribe
public synchronized void watchBannerJson(GameServerTickEvent tickEvent) {
if (GAME_OPTIONS.watchGachaConfig) {
try {
WatchKey watchKey = watchService.take();
for (WatchEvent<?> event : watchKey.pollEvents()) {
final Path changed = (Path) event.context();
if (changed.endsWith("Banners.json")) {
Grasscutter.getLogger()
.info("Change detected with banners.json. Reloading gacha config");
this.load();
}
}
boolean valid = watchKey.reset();
if (!valid) {
Grasscutter.getLogger()
.error(
"Unable to reset Gacha Manager Watch Key. Auto-reload of banners.json will no longer work.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private synchronized GetGachaInfoRsp createProto(Player player) {
GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345);
long currentTime = System.currentTimeMillis() / 1000L;
for (GachaBanner banner : getGachaBanners().values()) {
if ((banner.getEndTime() >= currentTime && banner.getBeginTime() <= currentTime)
|| (banner.getBannerType() == BannerType.STANDARD)) {
proto.addGachaInfoList(banner.toProto(player));
}
}
return proto.build();
}
public GetGachaInfoRsp toProto(Player player) {
return createProto(player);
}
private class BannerPools {
public int[] rateUpItems4;
public int[] rateUpItems5;
public int[] fallbackItems4Pool1;
public int[] fallbackItems4Pool2;
public int[] fallbackItems5Pool1;
public int[] fallbackItems5Pool2;
public BannerPools(GachaBanner banner) {
rateUpItems4 = banner.getRateUpItems4();
rateUpItems5 = banner.getRateUpItems5();
fallbackItems4Pool1 = banner.getFallbackItems4Pool1();
fallbackItems4Pool2 = banner.getFallbackItems4Pool2();
fallbackItems5Pool1 = banner.getFallbackItems5Pool1();
fallbackItems5Pool2 = banner.getFallbackItems5Pool2();
if (banner.isAutoStripRateUpFromFallback()) {
fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, rateUpItems4);
fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, rateUpItems4);
fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, rateUpItems5);
fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, rateUpItems5);
}
}
public void removeFromAllPools(int[] itemIds) {
rateUpItems4 = Utils.setSubtract(rateUpItems4, itemIds);
rateUpItems5 = Utils.setSubtract(rateUpItems5, itemIds);
fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, itemIds);
fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, itemIds);
fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, itemIds);
fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, itemIds);
}
}
}
package emu.grasscutter.game.gacha;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
import com.sun.nio.file.SensitivityWatchEventModifier;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.gacha.GachaBanner.BannerType;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.systems.InventorySystem;
import emu.grasscutter.net.proto.GachaItemOuterClass.GachaItem;
import emu.grasscutter.net.proto.GachaTransferItemOuterClass.GachaTransferItem;
import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.game.GameServerTickEvent;
import emu.grasscutter.server.packet.send.PacketDoGachaRsp;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import org.greenrobot.eventbus.Subscribe;
public class GachaSystem extends BaseGameSystem {
private static final int starglitterId = 221;
private static final int stardustId = 222;
private final Int2ObjectMap<GachaBanner> gachaBanners;
private WatchService watchService;
public GachaSystem(GameServer server) {
super(server);
this.gachaBanners = new Int2ObjectOpenHashMap<>();
this.load();
this.startWatcher(server);
}
public Int2ObjectMap<GachaBanner> getGachaBanners() {
return gachaBanners;
}
public int randomRange(int min, int max) { // Both are inclusive
return ThreadLocalRandom.current().nextInt(max - min + 1) + min;
}
public int getRandom(int[] array) {
return array[randomRange(0, array.length - 1)];
}
public synchronized void load() {
getGachaBanners().clear();
int autoScheduleId = 1000;
int autoSortId = 9000;
try {
List<GachaBanner> banners = DataLoader.loadTableToList("Banners", GachaBanner.class);
if (banners.size() > 0) {
for (GachaBanner banner : banners) {
banner.onLoad();
if (banner.isDeprecated()) {
Grasscutter.getLogger()
.error(
"A Banner has not been loaded because it contains one or more deprecated fields. Remove the fields mentioned above and reload.");
} else if (banner.isDisabled()) {
Grasscutter.getLogger().debug("A Banner has not been loaded because it is disabled.");
} else {
if (banner.scheduleId < 0) banner.scheduleId = autoScheduleId++;
if (banner.sortId < 0) banner.sortId = autoSortId--;
getGachaBanners().put(banner.scheduleId, banner);
}
}
Grasscutter.getLogger().debug("Banners successfully loaded.");
} else {
Grasscutter.getLogger().error("Unable to load banners. Banners size is 0.");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private synchronized int[] removeC6FromPool(int[] itemPool, Player player) {
IntList temp = new IntArrayList();
for (int itemId : itemPool) {
if (InventorySystem.checkPlayerAvatarConstellationLevel(player, itemId) < 6) {
temp.add(itemId);
}
}
return temp.toIntArray();
}
private synchronized int drawRoulette(int[] weights, int cutoff) {
// This follows the logic laid out in issue #183
// Simple weighted selection with an upper bound for the roll that cuts off trailing entries
// All weights must be >= 0
int total = 0;
for (int weight : weights) {
if (weight < 0) {
throw new IllegalArgumentException("Weights must be non-negative!");
}
total += weight;
}
int roll = ThreadLocalRandom.current().nextInt((total < cutoff) ? total : cutoff);
int subTotal = 0;
for (int i = 0; i < weights.length; i++) {
subTotal += weights[i];
if (roll < subTotal) {
return i;
}
}
// throw new IllegalStateException();
return 0; // This should only be reachable if total==0
}
private synchronized int doFallbackRarePull(
int[] fallback1,
int[] fallback2,
int rarity,
GachaBanner banner,
PlayerGachaBannerInfo gachaInfo) {
if (fallback1.length < 1) {
if (fallback2.length < 1) {
return getRandom(
(rarity == 5)
? GachaBanner.DEFAULT_FALLBACK_ITEMS_5_POOL_2
: GachaBanner.DEFAULT_FALLBACK_ITEMS_4_POOL_2);
} else {
return getRandom(fallback2);
}
} else if (fallback2.length < 1) {
return getRandom(fallback1);
} else { // Both pools are possible, use the pool balancer
int pityPool1 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 1));
int pityPool2 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 2));
int chosenPool =
switch ((pityPool1 >= pityPool2)
? 1
: 0) { // Larger weight must come first for the hard cutoff to function correctly
case 1 -> 1 + drawRoulette(new int[] {pityPool1, pityPool2}, 10000);
default -> 2 - drawRoulette(new int[] {pityPool2, pityPool1}, 10000);
};
return switch (chosenPool) {
case 1:
gachaInfo.setPityPool(rarity, 1, 0);
yield getRandom(fallback1);
default:
gachaInfo.setPityPool(rarity, 2, 0);
yield getRandom(fallback2);
};
}
}
private synchronized int doRarePull(
int[] featured,
int[] fallback1,
int[] fallback2,
int rarity,
GachaBanner banner,
PlayerGachaBannerInfo gachaInfo) {
int itemId = 0;
boolean epitomized =
(banner.hasEpitomized()) && (rarity == 5) && (gachaInfo.getWishItemId() != 0);
boolean pityEpitomized =
(gachaInfo.getFailedChosenItemPulls()
>= banner.getWishMaxProgress()); // Maximum fate points reached
boolean pityFeatured =
(gachaInfo.getFailedFeaturedItemPulls(rarity) >= 1); // Lost previous coinflip
boolean rollFeatured =
(this.randomRange(1, 100) <= banner.getEventChance(rarity)); // Won this coinflip
boolean pullFeatured = pityFeatured || rollFeatured;
if (epitomized && pityEpitomized) { // Auto pick item when epitomized points reached
gachaInfo.setFailedFeaturedItemPulls(
rarity, 0); // Epitomized item will always be a featured one
itemId = gachaInfo.getWishItemId();
} else {
if (pullFeatured && (featured.length > 0)) {
gachaInfo.setFailedFeaturedItemPulls(rarity, 0);
itemId = getRandom(featured);
} else {
gachaInfo.addFailedFeaturedItemPulls(
rarity,
1); // This could be moved into doFallbackRarePull but having it here makes it clearer
itemId = doFallbackRarePull(fallback1, fallback2, rarity, banner, gachaInfo);
}
}
if (epitomized) {
if (itemId == gachaInfo.getWishItemId()) { // Reset epitomized points when got wished item
gachaInfo.setFailedChosenItemPulls(0);
} else { // Add epitomized points if not get wished item
gachaInfo.addFailedChosenItemPulls(1);
}
}
return itemId;
}
private synchronized int doPull(
GachaBanner banner, PlayerGachaBannerInfo gachaInfo, BannerPools pools) {
// Pre-increment all pity pools (yes this makes all calculations assume 1-indexed pity)
gachaInfo.incPityAll();
int[] weights = {
banner.getWeight(5, gachaInfo.getPity5()), banner.getWeight(4, gachaInfo.getPity4()), 10000
};
int levelWon = 5 - drawRoulette(weights, 10000);
return switch (levelWon) {
case 5:
gachaInfo.setPity5(0);
yield doRarePull(
pools.rateUpItems5,
pools.fallbackItems5Pool1,
pools.fallbackItems5Pool2,
5,
banner,
gachaInfo);
case 4:
gachaInfo.setPity4(0);
yield doRarePull(
pools.rateUpItems4,
pools.fallbackItems4Pool1,
pools.fallbackItems4Pool2,
4,
banner,
gachaInfo);
default:
yield getRandom(banner.getFallbackItems3());
};
}
public synchronized void doPulls(Player player, int scheduleId, int times) {
// Sanity check
if (times != 10 && times != 1) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_INVALID_TIMES));
return;
}
Inventory inventory = player.getInventory();
if (inventory.getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times
> inventory.getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_ITEM_EXCEED_LIMIT));
return;
}
// Get banner
GachaBanner banner = this.getGachaBanners().get(scheduleId);
if (banner == null) {
player.sendPacket(new PacketDoGachaRsp());
return;
}
// Check against total limit
PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(banner);
int gachaTimesLimit = banner.getGachaTimesLimit();
if (gachaTimesLimit != Integer.MAX_VALUE
&& (gachaInfo.getTotalPulls() + times) > gachaTimesLimit) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_TIMES_LIMIT));
return;
}
// Spend currency
ItemParamData cost = banner.getCost(times);
if (cost.getCount() > 0 && !inventory.payItem(cost)) {
player.sendPacket(new PacketDoGachaRsp(Retcode.RET_GACHA_COST_ITEM_NOT_ENOUGH));
return;
}
// Add to character
gachaInfo.addTotalPulls(times);
BannerPools pools = new BannerPools(banner);
List<GachaItem> list = new ArrayList<>();
int stardust = 0, starglitter = 0;
if (banner.isRemoveC6FromPool()) { // The ultimate form of pity (non-vanilla)
pools.rateUpItems4 = removeC6FromPool(pools.rateUpItems4, player);
pools.rateUpItems5 = removeC6FromPool(pools.rateUpItems5, player);
pools.fallbackItems4Pool1 = removeC6FromPool(pools.fallbackItems4Pool1, player);
pools.fallbackItems4Pool2 = removeC6FromPool(pools.fallbackItems4Pool2, player);
pools.fallbackItems5Pool1 = removeC6FromPool(pools.fallbackItems5Pool1, player);
pools.fallbackItems5Pool2 = removeC6FromPool(pools.fallbackItems5Pool2, player);
}
for (int i = 0; i < times; i++) {
// Roll
int itemId = doPull(banner, gachaInfo, pools);
ItemData itemData = GameData.getItemDataMap().get(itemId);
if (itemData == null) {
continue; // Maybe we should bail out if an item fails instead of rolling the rest?
}
// Write gacha record
GachaRecord gachaRecord = new GachaRecord(itemId, player.getUid(), banner.getGachaType());
DatabaseHelper.saveGachaRecord(gachaRecord);
// Create gacha item
GachaItem.Builder gachaItem = GachaItem.newBuilder();
int addStardust = 0, addStarglitter = 0;
boolean isTransferItem = false;
// Const check
int constellation = InventorySystem.checkPlayerAvatarConstellationLevel(player, itemId);
switch (constellation) {
case -2: // Is weapon
switch (itemData.getRankLevel()) {
case 5 -> addStarglitter = 10;
case 4 -> addStarglitter = 2;
default -> addStardust = 15;
}
break;
case -1: // New character
gachaItem.setIsGachaItemNew(true);
break;
default:
if (constellation >= 6) { // C6, give consolation starglitter
addStarglitter = (itemData.getRankLevel() == 5) ? 25 : 5;
} else { // C0-C5, give constellation item
if (banner.isRemoveC6FromPool()
&& constellation
== 5) { // New C6, remove it from the pools so we don't get C7 in a 10pull
pools.removeFromAllPools(new int[] {itemId});
}
addStarglitter = (itemData.getRankLevel() == 5) ? 10 : 2;
int constItemId =
itemId + 100; // This may not hold true for future characters. Examples of strictly
// correct constellation item lookup are elsewhere for now.
boolean haveConstItem =
inventory.getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId) == null;
gachaItem.addTransferItems(
GachaTransferItem.newBuilder()
.setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1))
.setIsTransferItemNew(haveConstItem));
// inventory.addItem(constItemId, 1); // This is now managed by the avatar card item
// itself
}
isTransferItem = true;
break;
}
// Create item
GameItem item = new GameItem(itemData);
gachaItem.setGachaItem(item.toItemParam());
inventory.addItem(item);
stardust += addStardust;
starglitter += addStarglitter;
if (addStardust > 0) {
gachaItem.addTokenItemList(
ItemParam.newBuilder().setItemId(stardustId).setCount(addStardust));
}
if (addStarglitter > 0) {
ItemParam starglitterParam =
ItemParam.newBuilder().setItemId(starglitterId).setCount(addStarglitter).build();
if (isTransferItem) {
gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(starglitterParam));
}
gachaItem.addTokenItemList(starglitterParam);
}
list.add(gachaItem.build());
}
// Add stardust/starglitter
if (stardust > 0) {
inventory.addItem(stardustId, stardust);
}
if (starglitter > 0) {
inventory.addItem(starglitterId, starglitter);
}
// Packets
player.sendPacket(new PacketDoGachaRsp(banner, list, gachaInfo));
// Battle Pass trigger
player.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_GACHA_NUM, 0, times);
}
private synchronized void startWatcher(GameServer server) {
if (this.watchService == null) {
try {
this.watchService = FileSystems.getDefault().newWatchService();
FileUtils.getDataUserPath("")
.register(
watchService,
new WatchEvent.Kind[] {StandardWatchEventKinds.ENTRY_MODIFY},
SensitivityWatchEventModifier.HIGH);
} catch (Exception e) {
Grasscutter.getLogger()
.error(
"Unable to load the Gacha Manager Watch Service. If ServerOptions.watchGacha is true it will not auto-reload");
e.printStackTrace();
}
} else {
Grasscutter.getLogger().error("Cannot reinitialise watcher ");
}
}
@Subscribe
public synchronized void watchBannerJson(GameServerTickEvent tickEvent) {
if (GAME_OPTIONS.watchGachaConfig) {
try {
WatchKey watchKey = watchService.take();
for (WatchEvent<?> event : watchKey.pollEvents()) {
final Path changed = (Path) event.context();
if (changed.endsWith("Banners.json")) {
Grasscutter.getLogger()
.info("Change detected with banners.json. Reloading gacha config");
this.load();
}
}
boolean valid = watchKey.reset();
if (!valid) {
Grasscutter.getLogger()
.error(
"Unable to reset Gacha Manager Watch Key. Auto-reload of banners.json will no longer work.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private synchronized GetGachaInfoRsp createProto(Player player) {
GetGachaInfoRsp.Builder proto = GetGachaInfoRsp.newBuilder().setGachaRandom(12345);
long currentTime = System.currentTimeMillis() / 1000L;
for (GachaBanner banner : getGachaBanners().values()) {
if ((banner.getEndTime() >= currentTime && banner.getBeginTime() <= currentTime)
|| (banner.getBannerType() == BannerType.STANDARD)) {
proto.addGachaInfoList(banner.toProto(player));
}
}
return proto.build();
}
public GetGachaInfoRsp toProto(Player player) {
return createProto(player);
}
private class BannerPools {
public int[] rateUpItems4;
public int[] rateUpItems5;
public int[] fallbackItems4Pool1;
public int[] fallbackItems4Pool2;
public int[] fallbackItems5Pool1;
public int[] fallbackItems5Pool2;
public BannerPools(GachaBanner banner) {
rateUpItems4 = banner.getRateUpItems4();
rateUpItems5 = banner.getRateUpItems5();
fallbackItems4Pool1 = banner.getFallbackItems4Pool1();
fallbackItems4Pool2 = banner.getFallbackItems4Pool2();
fallbackItems5Pool1 = banner.getFallbackItems5Pool1();
fallbackItems5Pool2 = banner.getFallbackItems5Pool2();
if (banner.isAutoStripRateUpFromFallback()) {
fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, rateUpItems4);
fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, rateUpItems4);
fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, rateUpItems5);
fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, rateUpItems5);
}
}
public void removeFromAllPools(int[] itemIds) {
rateUpItems4 = Utils.setSubtract(rateUpItems4, itemIds);
rateUpItems5 = Utils.setSubtract(rateUpItems5, itemIds);
fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, itemIds);
fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, itemIds);
fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, itemIds);
fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, itemIds);
}
}
}

View File

@@ -1,122 +1,122 @@
package emu.grasscutter.game.gacha;
import dev.morphia.annotations.Entity;
import lombok.Getter;
import lombok.Setter;
@Entity
public class PlayerGachaBannerInfo {
@Getter @Setter private int totalPulls = 0;
@Getter @Setter private int pity5 = 0;
@Getter @Setter private int pity4 = 0;
private int failedFeaturedItemPulls = 0;
private int failedFeatured4ItemPulls = 0;
private int pity5Pool1 = 0;
private int pity5Pool2 = 0;
private int pity4Pool1 = 0;
private int pity4Pool2 = 0;
@Getter @Setter private int failedChosenItemPulls = 0;
@Getter @Setter private int wishItemId = 0;
public void addTotalPulls(int amount) {
this.totalPulls += amount;
}
public void addPity5(int amount) {
this.pity5 += amount;
}
public void addPity4(int amount) {
this.pity4 += amount;
}
public void addFailedChosenItemPulls(int amount) {
failedChosenItemPulls += amount;
}
public int getFailedFeaturedItemPulls(int rarity) {
return switch (rarity) {
case 4 -> failedFeatured4ItemPulls;
default -> failedFeaturedItemPulls; // 5
};
}
public void setFailedFeaturedItemPulls(int rarity, int amount) {
if (rarity == 4) {
failedFeatured4ItemPulls = amount;
} else {
failedFeaturedItemPulls = amount; // 5
}
}
public void addFailedFeaturedItemPulls(int rarity, int amount) {
if (rarity == 4) {
failedFeatured4ItemPulls += amount;
} else {
failedFeaturedItemPulls += amount; // 5
}
}
public int getPityPool(int rarity, int pool) {
return switch (rarity) {
case 4 -> switch (pool) {
case 1 -> pity4Pool1;
default -> pity4Pool2;
};
default -> switch (pool) {
case 1 -> pity5Pool1;
default -> pity5Pool2;
};
};
}
public void setPityPool(int rarity, int pool, int amount) {
switch (rarity) {
case 4:
if (pool == 1) {
pity4Pool1 = amount;
} else {
pity4Pool2 = amount;
}
break;
case 5:
default:
if (pool == 1) {
pity5Pool1 = amount;
} else {
pity5Pool2 = amount;
}
break;
}
}
public void addPityPool(int rarity, int pool, int amount) {
switch (rarity) {
case 4:
if (pool == 1) {
pity4Pool1 += amount;
} else {
pity4Pool2 += amount;
}
break;
case 5:
default:
if (pool == 1) {
pity5Pool1 += amount;
} else {
pity5Pool2 += amount;
}
break;
}
}
public void incPityAll() {
pity4++;
pity5++;
pity4Pool1++;
pity4Pool2++;
pity5Pool1++;
pity5Pool2++;
}
}
package emu.grasscutter.game.gacha;
import dev.morphia.annotations.Entity;
import lombok.Getter;
import lombok.Setter;
@Entity
public class PlayerGachaBannerInfo {
@Getter @Setter private int totalPulls = 0;
@Getter @Setter private int pity5 = 0;
@Getter @Setter private int pity4 = 0;
private int failedFeaturedItemPulls = 0;
private int failedFeatured4ItemPulls = 0;
private int pity5Pool1 = 0;
private int pity5Pool2 = 0;
private int pity4Pool1 = 0;
private int pity4Pool2 = 0;
@Getter @Setter private int failedChosenItemPulls = 0;
@Getter @Setter private int wishItemId = 0;
public void addTotalPulls(int amount) {
this.totalPulls += amount;
}
public void addPity5(int amount) {
this.pity5 += amount;
}
public void addPity4(int amount) {
this.pity4 += amount;
}
public void addFailedChosenItemPulls(int amount) {
failedChosenItemPulls += amount;
}
public int getFailedFeaturedItemPulls(int rarity) {
return switch (rarity) {
case 4 -> failedFeatured4ItemPulls;
default -> failedFeaturedItemPulls; // 5
};
}
public void setFailedFeaturedItemPulls(int rarity, int amount) {
if (rarity == 4) {
failedFeatured4ItemPulls = amount;
} else {
failedFeaturedItemPulls = amount; // 5
}
}
public void addFailedFeaturedItemPulls(int rarity, int amount) {
if (rarity == 4) {
failedFeatured4ItemPulls += amount;
} else {
failedFeaturedItemPulls += amount; // 5
}
}
public int getPityPool(int rarity, int pool) {
return switch (rarity) {
case 4 -> switch (pool) {
case 1 -> pity4Pool1;
default -> pity4Pool2;
};
default -> switch (pool) {
case 1 -> pity5Pool1;
default -> pity5Pool2;
};
};
}
public void setPityPool(int rarity, int pool, int amount) {
switch (rarity) {
case 4:
if (pool == 1) {
pity4Pool1 = amount;
} else {
pity4Pool2 = amount;
}
break;
case 5:
default:
if (pool == 1) {
pity5Pool1 = amount;
} else {
pity5Pool2 = amount;
}
break;
}
}
public void addPityPool(int rarity, int pool, int amount) {
switch (rarity) {
case 4:
if (pool == 1) {
pity4Pool1 += amount;
} else {
pity4Pool2 += amount;
}
break;
case 5:
default:
if (pool == 1) {
pity5Pool1 += amount;
} else {
pity5Pool2 += amount;
}
break;
}
}
public void incPityAll() {
pity4++;
pity5++;
pity4Pool1++;
pity4Pool2++;
pity5Pool1++;
pity5Pool2++;
}
}

View File

@@ -1,31 +1,31 @@
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.net.proto.FurnitureMakeDataOuterClass;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class FurnitureMakeSlotItem {
@Id int index;
int makeId;
int avatarId;
int beginTime;
int durTime;
public FurnitureMakeDataOuterClass.FurnitureMakeData toProto() {
return FurnitureMakeDataOuterClass.FurnitureMakeData.newBuilder()
.setIndex(index)
.setAvatarId(avatarId)
.setMakeId(makeId)
.setBeginTime(beginTime)
.setDurTime(durTime)
.build();
}
}
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.net.proto.FurnitureMakeDataOuterClass;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class FurnitureMakeSlotItem {
@Id int index;
int makeId;
int avatarId;
int beginTime;
int durTime;
public FurnitureMakeDataOuterClass.FurnitureMakeData toProto() {
return FurnitureMakeDataOuterClass.FurnitureMakeData.newBuilder()
.setIndex(index)
.setAvatarId(avatarId)
.setMakeId(makeId)
.setBeginTime(beginTime)
.setDurTime(durTime)
.build();
}
}

View File

@@ -1,358 +1,358 @@
package emu.grasscutter.game.home;
import dev.morphia.annotations.*;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.HomeWorldLevelData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity(value = "homes", useDiscriminator = false)
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class GameHome {
@Id String id;
@Indexed(options = @IndexOptions(unique = true))
long ownerUid;
@Transient Player player;
int level;
int exp;
int lastUpdatedTime;
int nextUpdateTime;
int storedCoin;
int storedFetterExp;
List<FurnitureMakeSlotItem> furnitureMakeSlotItemList;
ConcurrentHashMap<Integer, HomeSceneItem> sceneMap;
Set<Integer> unlockedHomeBgmList;
int enterHomeOption;
public static GameHome getByUid(Integer uid) {
var home = DatabaseHelper.getHomeByUid(uid);
if (home == null) {
home = GameHome.create(uid);
}
return home;
}
public static GameHome create(Integer uid) {
return GameHome.of()
.ownerUid(uid)
.level(1)
.sceneMap(new ConcurrentHashMap<>())
.unlockedHomeBgmList(new HashSet<>())
.build();
}
public void save() {
DatabaseHelper.saveHome(this);
}
public HomeSceneItem getHomeSceneItem(int sceneId) {
return sceneMap.computeIfAbsent(
sceneId,
e -> {
var defaultItem = GameData.getHomeworldDefaultSaveData().get(sceneId);
if (defaultItem != null) {
Grasscutter.getLogger()
.info("Set player {} home {} to initial setting", ownerUid, sceneId);
return HomeSceneItem.parseFrom(defaultItem, sceneId);
} else {
// Realm res missing bricks account, use default realm data to allow main house
defaultItem = GameData.getHomeworldDefaultSaveData().get(2001);
return HomeSceneItem.parseFrom(defaultItem, sceneId);
}
});
}
public void onOwnerLogin(Player player) {
if (this.player == null) this.player = player;
player.getSession().send(new PacketHomeBasicInfoNotify(player, false));
player.getSession().send(new PacketPlayerHomeCompInfoNotify(player));
player.getSession().send(new PacketHomeComfortInfoNotify(player));
player.getSession().send(new PacketFurnitureCurModuleArrangeCountNotify());
player.getSession().send(new PacketHomeMarkPointNotify(player));
player.getSession().send(new PacketHomeAllUnlockedBgmIdListNotify(player));
checkAccumulatedResources(player);
player.getSession().send(new PacketHomeResourceNotify(player));
}
// Tell the client the reward is claimed or realm unlocked
public void onClaimReward(Player player) {
player.getSession().send(new PacketPlayerHomeCompInfoNotify(player));
}
public Player getPlayer() {
if (this.player == null)
this.player = Grasscutter.getGameServer().getPlayerByUid((int) this.ownerUid, true);
return this.player;
}
public HomeWorldLevelData getLevelData() {
return GameData.getHomeWorldLevelDataMap().get(level);
}
public boolean addUnlockedHomeBgm(int homeBgmId) {
if (!getUnlockedHomeBgmList().add(homeBgmId)) return false;
var player = this.getPlayer();
player.sendPacket(new PacketHomeNewUnlockedBgmIdListNotify(homeBgmId));
player.sendPacket(new PacketHomeAllUnlockedBgmIdListNotify(player));
save();
return true;
}
public Set<Integer> getUnlockedHomeBgmList() {
if (this.unlockedHomeBgmList == null) {
this.unlockedHomeBgmList = new HashSet<>();
}
if (this.unlockedHomeBgmList.addAll(getDefaultUnlockedHomeBgmIds())) {
save();
}
return this.unlockedHomeBgmList;
}
private Set<Integer> getDefaultUnlockedHomeBgmIds() {
return GameData.getHomeWorldBgmDataMap().int2ObjectEntrySet().stream()
.filter(e -> e.getValue().isDefaultUnlock())
.map(Int2ObjectMap.Entry::getIntKey)
.collect(Collectors.toUnmodifiableSet());
}
// Same as Player.java addExpDirectly
public void addExp(Player player, int count) {
exp += count;
int reqExp = getExpRequired(level);
while (exp >= reqExp && reqExp > 0) {
exp -= reqExp;
level += 1;
reqExp = getExpRequired(level);
// Update client level and exp
player.getSession().send(new PacketHomeBasicInfoNotify(player, false));
}
// Update client home
onOwnerLogin(player);
}
private void checkAccumulatedResources(Player player) {
int clientTime = (int) ZonedDateTime.now().toEpochSecond();
int owedRewards = 0;
// Don't owe if previous update hasn't passed
if (nextUpdateTime > clientTime) {
return;
}
if (lastUpdatedTime == 0) {
lastUpdatedTime = clientTime;
}
// Calculate number of owed rewards
owedRewards = 1 + ((clientTime - nextUpdateTime) / 3600);
// Ensure next update is at top of the hour
nextUpdateTime =
(int) ZonedDateTime.now().plusHours(1).truncatedTo(ChronoUnit.HOURS).toEpochSecond();
// Get resources
var hourlyResources = getComfortResources(player);
var owedCoin = hourlyResources.get(0) * owedRewards;
var owedFetter = hourlyResources.get(1) * owedRewards;
// Update stored amounts
storeResources(player, owedCoin, owedFetter);
}
public void takeHomeCoin(Player player) {
player.getInventory().addItem(204, storedCoin);
storedCoin = 0;
save();
player.getSession().send(new PacketHomeResourceNotify(player));
}
public void takeHomeFetter(Player player) {
List<Integer> invitedAvatars = new ArrayList<>();
// Outdoors avatars
sceneMap
.get(player.getCurrentRealmId() + 2000)
.getBlockItems()
.forEach(
(i, e) -> {
e.getDeployNPCList()
.forEach(
id -> {
invitedAvatars.add(id.getAvatarId());
});
});
// Check as realm 5 inside is not in defaults and will be null
if (Objects.nonNull(sceneMap.get(player.getCurrentRealmId() + 2200))) {
// Indoors avatars
sceneMap
.get(player.getCurrentRealmId() + 2200)
.getBlockItems()
.forEach(
(i, e) -> {
e.getDeployNPCList()
.forEach(
id -> {
invitedAvatars.add(id.getAvatarId());
});
});
}
// Add exp to all avatars
invitedAvatars.forEach(
id -> {
var avatar = player.getAvatars().getAvatarById(id);
player
.getServer()
.getInventorySystem()
.upgradeAvatarFetterLevel(player, avatar, storedFetterExp);
});
storedFetterExp = 0;
save();
player.getSession().send(new PacketHomeResourceNotify(player));
}
public void updateHourlyResources(Player player) {
int clientTime = (int) ZonedDateTime.now().toEpochSecond();
// Check if resources can update
if (nextUpdateTime > clientTime) {
return;
}
// If no update has occurred before
if (lastUpdatedTime == 0) {
lastUpdatedTime = clientTime;
}
// Update stored resources
storeResources(player, 0, 0);
lastUpdatedTime = clientTime;
nextUpdateTime =
(int) ZonedDateTime.now().plusHours(1).truncatedTo(ChronoUnit.HOURS).toEpochSecond();
save();
// Send packet
player.getSession().send(new PacketHomeResourceNotify(player));
}
public void storeResources(Player player, int owedCoin, int owedFetter) {
// Get max values
var maxCoin = getMaxCoin(level);
var maxFetter = getMaxFetter(level);
int newCoin = 0;
int newFetter = 0;
// Check if resources are already max
if (storedCoin >= maxCoin && storedFetterExp >= maxFetter) {
return;
}
// Get resources
var hourlyResources = getComfortResources(player);
// Update home coin
if (storedCoin < maxCoin) {
// Check if owed or hourly
if (owedCoin == 0) {
newCoin = storedCoin + hourlyResources.get(0);
} else {
newCoin = storedCoin + owedCoin;
}
// Ensure max is not exceeded
storedCoin = (maxCoin >= newCoin) ? newCoin : maxCoin;
}
// Update fetter exp
if (storedFetterExp < maxFetter) {
// Check if owed or hourly
if (owedFetter == 0) {
newFetter = storedFetterExp + hourlyResources.get(1);
} else {
newFetter = storedFetterExp + owedFetter;
}
// Ensure max is not exceeded
storedFetterExp = (maxFetter >= newFetter) ? newFetter : maxFetter;
}
save();
}
public List<Integer> getComfortResources(Player player) {
List<Integer> allHomesComfort = new ArrayList<>();
int highestComfort = 0;
// Use HomeComfortInfoNotify data since comfort value isn't stored
if (player.getRealmList() == null) {
return List.of(0, 0);
}
// Calculate comfort value for each home
for (int moduleId : player.getRealmList()) {
var homeScene = player.getHome().getHomeSceneItem(moduleId + 2000);
allHomesComfort.add(homeScene.calComfort());
}
// Get highest comfort value
highestComfort = Collections.max(allHomesComfort);
// Determine hourly resources
if (highestComfort >= 20000) {
return List.of(30, 5);
} else if (highestComfort >= 15000) {
return List.of(28, 5);
} else if (highestComfort >= 12000) {
return List.of(26, 5);
} else if (highestComfort >= 10000) {
return List.of(24, 4);
} else if (highestComfort >= 8000) {
return List.of(22, 4);
} else if (highestComfort >= 6000) {
return List.of(20, 4);
} else if (highestComfort >= 4500) {
return List.of(16, 3);
} else if (highestComfort >= 3000) {
return List.of(12, 3);
} else if (highestComfort >= 2000) {
return List.of(8, 2);
} else return List.of(4, 2);
}
private int getExpRequired(int level) {
HomeWorldLevelData levelData = GameData.getHomeWorldLevelDataMap().get(level);
return levelData != null ? levelData.getExp() : 0;
}
public int getMaxCoin(int level) {
var levelData = GameData.getHomeWorldLevelDataMap().get(level);
return levelData != null ? levelData.getHomeCoinStoreLimit() : 0;
}
public int getMaxFetter(int level) {
var levelData = GameData.getHomeWorldLevelDataMap().get(level);
return levelData != null ? levelData.getHomeFetterExpStoreLimit() : 0;
}
}
package emu.grasscutter.game.home;
import dev.morphia.annotations.*;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.HomeWorldLevelData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity(value = "homes", useDiscriminator = false)
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class GameHome {
@Id String id;
@Indexed(options = @IndexOptions(unique = true))
long ownerUid;
@Transient Player player;
int level;
int exp;
int lastUpdatedTime;
int nextUpdateTime;
int storedCoin;
int storedFetterExp;
List<FurnitureMakeSlotItem> furnitureMakeSlotItemList;
ConcurrentHashMap<Integer, HomeSceneItem> sceneMap;
Set<Integer> unlockedHomeBgmList;
int enterHomeOption;
public static GameHome getByUid(Integer uid) {
var home = DatabaseHelper.getHomeByUid(uid);
if (home == null) {
home = GameHome.create(uid);
}
return home;
}
public static GameHome create(Integer uid) {
return GameHome.of()
.ownerUid(uid)
.level(1)
.sceneMap(new ConcurrentHashMap<>())
.unlockedHomeBgmList(new HashSet<>())
.build();
}
public void save() {
DatabaseHelper.saveHome(this);
}
public HomeSceneItem getHomeSceneItem(int sceneId) {
return sceneMap.computeIfAbsent(
sceneId,
e -> {
var defaultItem = GameData.getHomeworldDefaultSaveData().get(sceneId);
if (defaultItem != null) {
Grasscutter.getLogger()
.info("Set player {} home {} to initial setting", ownerUid, sceneId);
return HomeSceneItem.parseFrom(defaultItem, sceneId);
} else {
// Realm res missing bricks account, use default realm data to allow main house
defaultItem = GameData.getHomeworldDefaultSaveData().get(2001);
return HomeSceneItem.parseFrom(defaultItem, sceneId);
}
});
}
public void onOwnerLogin(Player player) {
if (this.player == null) this.player = player;
player.getSession().send(new PacketHomeBasicInfoNotify(player, false));
player.getSession().send(new PacketPlayerHomeCompInfoNotify(player));
player.getSession().send(new PacketHomeComfortInfoNotify(player));
player.getSession().send(new PacketFurnitureCurModuleArrangeCountNotify());
player.getSession().send(new PacketHomeMarkPointNotify(player));
player.getSession().send(new PacketHomeAllUnlockedBgmIdListNotify(player));
checkAccumulatedResources(player);
player.getSession().send(new PacketHomeResourceNotify(player));
}
// Tell the client the reward is claimed or realm unlocked
public void onClaimReward(Player player) {
player.getSession().send(new PacketPlayerHomeCompInfoNotify(player));
}
public Player getPlayer() {
if (this.player == null)
this.player = Grasscutter.getGameServer().getPlayerByUid((int) this.ownerUid, true);
return this.player;
}
public HomeWorldLevelData getLevelData() {
return GameData.getHomeWorldLevelDataMap().get(level);
}
public boolean addUnlockedHomeBgm(int homeBgmId) {
if (!getUnlockedHomeBgmList().add(homeBgmId)) return false;
var player = this.getPlayer();
player.sendPacket(new PacketHomeNewUnlockedBgmIdListNotify(homeBgmId));
player.sendPacket(new PacketHomeAllUnlockedBgmIdListNotify(player));
save();
return true;
}
public Set<Integer> getUnlockedHomeBgmList() {
if (this.unlockedHomeBgmList == null) {
this.unlockedHomeBgmList = new HashSet<>();
}
if (this.unlockedHomeBgmList.addAll(getDefaultUnlockedHomeBgmIds())) {
save();
}
return this.unlockedHomeBgmList;
}
private Set<Integer> getDefaultUnlockedHomeBgmIds() {
return GameData.getHomeWorldBgmDataMap().int2ObjectEntrySet().stream()
.filter(e -> e.getValue().isDefaultUnlock())
.map(Int2ObjectMap.Entry::getIntKey)
.collect(Collectors.toUnmodifiableSet());
}
// Same as Player.java addExpDirectly
public void addExp(Player player, int count) {
exp += count;
int reqExp = getExpRequired(level);
while (exp >= reqExp && reqExp > 0) {
exp -= reqExp;
level += 1;
reqExp = getExpRequired(level);
// Update client level and exp
player.getSession().send(new PacketHomeBasicInfoNotify(player, false));
}
// Update client home
onOwnerLogin(player);
}
private void checkAccumulatedResources(Player player) {
int clientTime = (int) ZonedDateTime.now().toEpochSecond();
int owedRewards = 0;
// Don't owe if previous update hasn't passed
if (nextUpdateTime > clientTime) {
return;
}
if (lastUpdatedTime == 0) {
lastUpdatedTime = clientTime;
}
// Calculate number of owed rewards
owedRewards = 1 + ((clientTime - nextUpdateTime) / 3600);
// Ensure next update is at top of the hour
nextUpdateTime =
(int) ZonedDateTime.now().plusHours(1).truncatedTo(ChronoUnit.HOURS).toEpochSecond();
// Get resources
var hourlyResources = getComfortResources(player);
var owedCoin = hourlyResources.get(0) * owedRewards;
var owedFetter = hourlyResources.get(1) * owedRewards;
// Update stored amounts
storeResources(player, owedCoin, owedFetter);
}
public void takeHomeCoin(Player player) {
player.getInventory().addItem(204, storedCoin);
storedCoin = 0;
save();
player.getSession().send(new PacketHomeResourceNotify(player));
}
public void takeHomeFetter(Player player) {
List<Integer> invitedAvatars = new ArrayList<>();
// Outdoors avatars
sceneMap
.get(player.getCurrentRealmId() + 2000)
.getBlockItems()
.forEach(
(i, e) -> {
e.getDeployNPCList()
.forEach(
id -> {
invitedAvatars.add(id.getAvatarId());
});
});
// Check as realm 5 inside is not in defaults and will be null
if (Objects.nonNull(sceneMap.get(player.getCurrentRealmId() + 2200))) {
// Indoors avatars
sceneMap
.get(player.getCurrentRealmId() + 2200)
.getBlockItems()
.forEach(
(i, e) -> {
e.getDeployNPCList()
.forEach(
id -> {
invitedAvatars.add(id.getAvatarId());
});
});
}
// Add exp to all avatars
invitedAvatars.forEach(
id -> {
var avatar = player.getAvatars().getAvatarById(id);
player
.getServer()
.getInventorySystem()
.upgradeAvatarFetterLevel(player, avatar, storedFetterExp);
});
storedFetterExp = 0;
save();
player.getSession().send(new PacketHomeResourceNotify(player));
}
public void updateHourlyResources(Player player) {
int clientTime = (int) ZonedDateTime.now().toEpochSecond();
// Check if resources can update
if (nextUpdateTime > clientTime) {
return;
}
// If no update has occurred before
if (lastUpdatedTime == 0) {
lastUpdatedTime = clientTime;
}
// Update stored resources
storeResources(player, 0, 0);
lastUpdatedTime = clientTime;
nextUpdateTime =
(int) ZonedDateTime.now().plusHours(1).truncatedTo(ChronoUnit.HOURS).toEpochSecond();
save();
// Send packet
player.getSession().send(new PacketHomeResourceNotify(player));
}
public void storeResources(Player player, int owedCoin, int owedFetter) {
// Get max values
var maxCoin = getMaxCoin(level);
var maxFetter = getMaxFetter(level);
int newCoin = 0;
int newFetter = 0;
// Check if resources are already max
if (storedCoin >= maxCoin && storedFetterExp >= maxFetter) {
return;
}
// Get resources
var hourlyResources = getComfortResources(player);
// Update home coin
if (storedCoin < maxCoin) {
// Check if owed or hourly
if (owedCoin == 0) {
newCoin = storedCoin + hourlyResources.get(0);
} else {
newCoin = storedCoin + owedCoin;
}
// Ensure max is not exceeded
storedCoin = (maxCoin >= newCoin) ? newCoin : maxCoin;
}
// Update fetter exp
if (storedFetterExp < maxFetter) {
// Check if owed or hourly
if (owedFetter == 0) {
newFetter = storedFetterExp + hourlyResources.get(1);
} else {
newFetter = storedFetterExp + owedFetter;
}
// Ensure max is not exceeded
storedFetterExp = (maxFetter >= newFetter) ? newFetter : maxFetter;
}
save();
}
public List<Integer> getComfortResources(Player player) {
List<Integer> allHomesComfort = new ArrayList<>();
int highestComfort = 0;
// Use HomeComfortInfoNotify data since comfort value isn't stored
if (player.getRealmList() == null) {
return List.of(0, 0);
}
// Calculate comfort value for each home
for (int moduleId : player.getRealmList()) {
var homeScene = player.getHome().getHomeSceneItem(moduleId + 2000);
allHomesComfort.add(homeScene.calComfort());
}
// Get highest comfort value
highestComfort = Collections.max(allHomesComfort);
// Determine hourly resources
if (highestComfort >= 20000) {
return List.of(30, 5);
} else if (highestComfort >= 15000) {
return List.of(28, 5);
} else if (highestComfort >= 12000) {
return List.of(26, 5);
} else if (highestComfort >= 10000) {
return List.of(24, 4);
} else if (highestComfort >= 8000) {
return List.of(22, 4);
} else if (highestComfort >= 6000) {
return List.of(20, 4);
} else if (highestComfort >= 4500) {
return List.of(16, 3);
} else if (highestComfort >= 3000) {
return List.of(12, 3);
} else if (highestComfort >= 2000) {
return List.of(8, 2);
} else return List.of(4, 2);
}
private int getExpRequired(int level) {
HomeWorldLevelData levelData = GameData.getHomeWorldLevelDataMap().get(level);
return levelData != null ? levelData.getExp() : 0;
}
public int getMaxCoin(int level) {
var levelData = GameData.getHomeWorldLevelDataMap().get(level);
return levelData != null ? levelData.getHomeCoinStoreLimit() : 0;
}
public int getMaxFetter(int level) {
var levelData = GameData.getHomeWorldLevelDataMap().get(level);
return levelData != null ? levelData.getHomeFetterExpStoreLimit() : 0;
}
}

View File

@@ -1,35 +1,35 @@
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.HomeAnimalDataOuterClass;
import emu.grasscutter.utils.Position;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class HomeAnimalItem {
int furnitureId;
Position spawnPos;
Position spawnRot;
public static HomeAnimalItem parseFrom(HomeAnimalDataOuterClass.HomeAnimalData homeAnimalData) {
return HomeAnimalItem.of()
.furnitureId(homeAnimalData.getFurnitureId())
.spawnPos(new Position(homeAnimalData.getSpawnPos()))
.spawnRot(new Position(homeAnimalData.getSpawnRot()))
.build();
}
public HomeAnimalDataOuterClass.HomeAnimalData toProto() {
return HomeAnimalDataOuterClass.HomeAnimalData.newBuilder()
.setFurnitureId(furnitureId)
.setSpawnPos(spawnPos.toProto())
.setSpawnRot(spawnRot.toProto())
.build();
}
}
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.HomeAnimalDataOuterClass;
import emu.grasscutter.utils.Position;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class HomeAnimalItem {
int furnitureId;
Position spawnPos;
Position spawnRot;
public static HomeAnimalItem parseFrom(HomeAnimalDataOuterClass.HomeAnimalData homeAnimalData) {
return HomeAnimalItem.of()
.furnitureId(homeAnimalData.getFurnitureId())
.spawnPos(new Position(homeAnimalData.getSpawnPos()))
.spawnRot(new Position(homeAnimalData.getSpawnRot()))
.build();
}
public HomeAnimalDataOuterClass.HomeAnimalData toProto() {
return HomeAnimalDataOuterClass.HomeAnimalData.newBuilder()
.setFurnitureId(furnitureId)
.setSpawnPos(spawnPos.toProto())
.setSpawnRot(spawnRot.toProto())
.build();
}
}

View File

@@ -1,87 +1,87 @@
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.net.proto.HomeBlockArrangementInfoOuterClass.HomeBlockArrangementInfo;
import java.util.List;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@Builder(builderMethodName = "of")
@FieldDefaults(level = AccessLevel.PRIVATE)
public class HomeBlockItem {
@Id int blockId;
boolean unlocked;
List<HomeFurnitureItem> deployFurnitureList;
List<HomeFurnitureItem> persistentFurnitureList;
List<HomeAnimalItem> deployAnimalList;
List<HomeNPCItem> deployNPCList;
public static HomeBlockItem parseFrom(HomeworldDefaultSaveData.HomeBlock homeBlock) {
// create from default setting
return HomeBlockItem.of()
.blockId(homeBlock.getBlockId())
.unlocked(homeBlock.getFurnitures() != null)
.deployFurnitureList(
homeBlock.getFurnitures() == null
? List.of()
: homeBlock.getFurnitures().stream().map(HomeFurnitureItem::parseFrom).toList())
.persistentFurnitureList(
homeBlock.getPersistentFurnitures() == null
? List.of()
: homeBlock.getPersistentFurnitures().stream()
.map(HomeFurnitureItem::parseFrom)
.toList())
.deployAnimalList(List.of())
.deployNPCList(List.of())
.build();
}
public void update(HomeBlockArrangementInfo homeBlockArrangementInfo) {
this.blockId = homeBlockArrangementInfo.getBlockId();
this.deployFurnitureList =
homeBlockArrangementInfo.getDeployFurniureListList().stream()
.map(HomeFurnitureItem::parseFrom)
.toList();
this.persistentFurnitureList =
homeBlockArrangementInfo.getPersistentFurnitureListList().stream()
.map(HomeFurnitureItem::parseFrom)
.toList();
this.deployAnimalList =
homeBlockArrangementInfo.getDeployAnimalListList().stream()
.map(HomeAnimalItem::parseFrom)
.toList();
this.deployNPCList =
homeBlockArrangementInfo.getDeployNpcListList().stream()
.map(HomeNPCItem::parseFrom)
.toList();
}
public int calComfort() {
return this.deployFurnitureList.stream().mapToInt(HomeFurnitureItem::getComfort).sum();
}
public HomeBlockArrangementInfo toProto() {
var proto =
HomeBlockArrangementInfo.newBuilder()
.setBlockId(blockId)
.setIsUnlocked(unlocked)
.setComfortValue(calComfort());
this.deployFurnitureList.forEach(f -> proto.addDeployFurniureList(f.toProto()));
this.persistentFurnitureList.forEach(f -> proto.addPersistentFurnitureList(f.toProto()));
this.deployAnimalList.forEach(f -> proto.addDeployAnimalList(f.toProto()));
this.deployNPCList.forEach(f -> proto.addDeployNpcList(f.toProto()));
return proto.build();
}
}
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.net.proto.HomeBlockArrangementInfoOuterClass.HomeBlockArrangementInfo;
import java.util.List;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@Builder(builderMethodName = "of")
@FieldDefaults(level = AccessLevel.PRIVATE)
public class HomeBlockItem {
@Id int blockId;
boolean unlocked;
List<HomeFurnitureItem> deployFurnitureList;
List<HomeFurnitureItem> persistentFurnitureList;
List<HomeAnimalItem> deployAnimalList;
List<HomeNPCItem> deployNPCList;
public static HomeBlockItem parseFrom(HomeworldDefaultSaveData.HomeBlock homeBlock) {
// create from default setting
return HomeBlockItem.of()
.blockId(homeBlock.getBlockId())
.unlocked(homeBlock.getFurnitures() != null)
.deployFurnitureList(
homeBlock.getFurnitures() == null
? List.of()
: homeBlock.getFurnitures().stream().map(HomeFurnitureItem::parseFrom).toList())
.persistentFurnitureList(
homeBlock.getPersistentFurnitures() == null
? List.of()
: homeBlock.getPersistentFurnitures().stream()
.map(HomeFurnitureItem::parseFrom)
.toList())
.deployAnimalList(List.of())
.deployNPCList(List.of())
.build();
}
public void update(HomeBlockArrangementInfo homeBlockArrangementInfo) {
this.blockId = homeBlockArrangementInfo.getBlockId();
this.deployFurnitureList =
homeBlockArrangementInfo.getDeployFurniureListList().stream()
.map(HomeFurnitureItem::parseFrom)
.toList();
this.persistentFurnitureList =
homeBlockArrangementInfo.getPersistentFurnitureListList().stream()
.map(HomeFurnitureItem::parseFrom)
.toList();
this.deployAnimalList =
homeBlockArrangementInfo.getDeployAnimalListList().stream()
.map(HomeAnimalItem::parseFrom)
.toList();
this.deployNPCList =
homeBlockArrangementInfo.getDeployNpcListList().stream()
.map(HomeNPCItem::parseFrom)
.toList();
}
public int calComfort() {
return this.deployFurnitureList.stream().mapToInt(HomeFurnitureItem::getComfort).sum();
}
public HomeBlockArrangementInfo toProto() {
var proto =
HomeBlockArrangementInfo.newBuilder()
.setBlockId(blockId)
.setIsUnlocked(unlocked)
.setComfortValue(calComfort());
this.deployFurnitureList.forEach(f -> proto.addDeployFurniureList(f.toProto()));
this.persistentFurnitureList.forEach(f -> proto.addPersistentFurnitureList(f.toProto()));
this.deployAnimalList.forEach(f -> proto.addDeployAnimalList(f.toProto()));
this.deployNPCList.forEach(f -> proto.addDeployNpcList(f.toProto()));
return proto.build();
}
}

View File

@@ -1,82 +1,82 @@
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.net.proto.HomeFurnitureDataOuterClass;
import emu.grasscutter.net.proto.HomeMarkPointFurnitureDataOuterClass;
import emu.grasscutter.utils.Position;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class HomeFurnitureItem {
int furnitureId;
int guid;
int parentFurnitureIndex;
Position spawnPos;
Position spawnRot;
int version;
public static HomeFurnitureItem parseFrom(
HomeFurnitureDataOuterClass.HomeFurnitureData homeFurnitureData) {
return HomeFurnitureItem.of()
.furnitureId(homeFurnitureData.getFurnitureId())
.guid(homeFurnitureData.getGuid())
.parentFurnitureIndex(homeFurnitureData.getParentFurnitureIndex())
.spawnPos(new Position(homeFurnitureData.getSpawnPos()))
.spawnRot(new Position(homeFurnitureData.getSpawnRot()))
.version(homeFurnitureData.getVersion())
.build();
}
public static HomeFurnitureItem parseFrom(HomeworldDefaultSaveData.HomeFurniture homeFurniture) {
return HomeFurnitureItem.of()
.furnitureId(homeFurniture.getId())
.parentFurnitureIndex(1)
.spawnPos(homeFurniture.getPos() == null ? new Position() : homeFurniture.getPos())
.spawnRot(homeFurniture.getRot() == null ? new Position() : homeFurniture.getRot())
.build();
}
public HomeFurnitureDataOuterClass.HomeFurnitureData toProto() {
return HomeFurnitureDataOuterClass.HomeFurnitureData.newBuilder()
.setFurnitureId(furnitureId)
.setGuid(guid)
.setParentFurnitureIndex(parentFurnitureIndex)
.setSpawnPos(spawnPos.toProto())
.setSpawnRot(spawnRot.toProto())
.setVersion(version)
.build();
}
public HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData toMarkPointProto(
int type) {
return HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData.newBuilder()
.setFurnitureId(furnitureId)
.setGuid(guid)
.setFurnitureType(type)
.setPos(spawnPos.toProto())
// TODO NPC and farm
.build();
}
public ItemData getAsItem() {
return GameData.getItemDataMap().get(this.furnitureId);
}
public int getComfort() {
var item = getAsItem();
if (item == null) {
return 0;
}
return item.getComfort();
}
}
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.net.proto.HomeFurnitureDataOuterClass;
import emu.grasscutter.net.proto.HomeMarkPointFurnitureDataOuterClass;
import emu.grasscutter.utils.Position;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class HomeFurnitureItem {
int furnitureId;
int guid;
int parentFurnitureIndex;
Position spawnPos;
Position spawnRot;
int version;
public static HomeFurnitureItem parseFrom(
HomeFurnitureDataOuterClass.HomeFurnitureData homeFurnitureData) {
return HomeFurnitureItem.of()
.furnitureId(homeFurnitureData.getFurnitureId())
.guid(homeFurnitureData.getGuid())
.parentFurnitureIndex(homeFurnitureData.getParentFurnitureIndex())
.spawnPos(new Position(homeFurnitureData.getSpawnPos()))
.spawnRot(new Position(homeFurnitureData.getSpawnRot()))
.version(homeFurnitureData.getVersion())
.build();
}
public static HomeFurnitureItem parseFrom(HomeworldDefaultSaveData.HomeFurniture homeFurniture) {
return HomeFurnitureItem.of()
.furnitureId(homeFurniture.getId())
.parentFurnitureIndex(1)
.spawnPos(homeFurniture.getPos() == null ? new Position() : homeFurniture.getPos())
.spawnRot(homeFurniture.getRot() == null ? new Position() : homeFurniture.getRot())
.build();
}
public HomeFurnitureDataOuterClass.HomeFurnitureData toProto() {
return HomeFurnitureDataOuterClass.HomeFurnitureData.newBuilder()
.setFurnitureId(furnitureId)
.setGuid(guid)
.setParentFurnitureIndex(parentFurnitureIndex)
.setSpawnPos(spawnPos.toProto())
.setSpawnRot(spawnRot.toProto())
.setVersion(version)
.build();
}
public HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData toMarkPointProto(
int type) {
return HomeMarkPointFurnitureDataOuterClass.HomeMarkPointFurnitureData.newBuilder()
.setFurnitureId(furnitureId)
.setGuid(guid)
.setFurnitureType(type)
.setPos(spawnPos.toProto())
// TODO NPC and farm
.build();
}
public ItemData getAsItem() {
return GameData.getItemDataMap().get(this.furnitureId);
}
public int getComfort() {
var item = getAsItem();
if (item == null) {
return 0;
}
return item.getComfort();
}
}

View File

@@ -1,38 +1,38 @@
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.HomeNpcDataOuterClass;
import emu.grasscutter.utils.Position;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class HomeNPCItem {
int avatarId;
Position spawnPos;
Position spawnRot;
int costumeId;
public static HomeNPCItem parseFrom(HomeNpcDataOuterClass.HomeNpcData homeNpcData) {
return HomeNPCItem.of()
.avatarId(homeNpcData.getAvatarId())
.spawnPos(new Position(homeNpcData.getSpawnPos()))
.spawnRot(new Position(homeNpcData.getSpawnRot()))
.costumeId(homeNpcData.getCostumeId())
.build();
}
public HomeNpcDataOuterClass.HomeNpcData toProto() {
return HomeNpcDataOuterClass.HomeNpcData.newBuilder()
.setAvatarId(avatarId)
.setSpawnPos(spawnPos.toProto())
.setSpawnRot(spawnRot.toProto())
.setCostumeId(costumeId)
.build();
}
}
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.HomeNpcDataOuterClass;
import emu.grasscutter.utils.Position;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class HomeNPCItem {
int avatarId;
Position spawnPos;
Position spawnRot;
int costumeId;
public static HomeNPCItem parseFrom(HomeNpcDataOuterClass.HomeNpcData homeNpcData) {
return HomeNPCItem.of()
.avatarId(homeNpcData.getAvatarId())
.spawnPos(new Position(homeNpcData.getSpawnPos()))
.spawnRot(new Position(homeNpcData.getSpawnRot()))
.costumeId(homeNpcData.getCostumeId())
.build();
}
public HomeNpcDataOuterClass.HomeNpcData toProto() {
return HomeNpcDataOuterClass.HomeNpcData.newBuilder()
.setAvatarId(avatarId)
.setSpawnPos(spawnPos.toProto())
.setSpawnRot(spawnRot.toProto())
.setCostumeId(costumeId)
.build();
}
}

View File

@@ -1,96 +1,96 @@
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.net.proto.HomeSceneArrangementInfoOuterClass.HomeSceneArrangementInfo;
import emu.grasscutter.utils.Position;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@Builder(builderMethodName = "of")
@FieldDefaults(level = AccessLevel.PRIVATE)
public class HomeSceneItem {
@Id int sceneId;
Map<Integer, HomeBlockItem> blockItems;
Position bornPos;
Position bornRot;
Position djinnPos;
int homeBgmId;
HomeFurnitureItem mainHouse;
int tmpVersion;
public static HomeSceneItem parseFrom(HomeworldDefaultSaveData defaultItem, int sceneId) {
return HomeSceneItem.of()
.sceneId(sceneId)
.blockItems(
defaultItem.getHomeBlockLists().stream()
.map(HomeBlockItem::parseFrom)
.collect(Collectors.toMap(HomeBlockItem::getBlockId, y -> y)))
.bornPos(defaultItem.getBornPos())
.bornRot(defaultItem.getBornRot() == null ? new Position() : defaultItem.getBornRot())
.djinnPos(defaultItem.getDjinPos() == null ? new Position() : defaultItem.getDjinPos())
.mainHouse(
defaultItem.getMainhouse() == null
? null
: HomeFurnitureItem.parseFrom(defaultItem.getMainhouse()))
.build();
}
public void update(HomeSceneArrangementInfo arrangementInfo) {
for (var blockItem : arrangementInfo.getBlockArrangementInfoListList()) {
var block = this.blockItems.get(blockItem.getBlockId());
if (block == null) {
Grasscutter.getLogger().warn("Could not found the Home Block {}", blockItem.getBlockId());
continue;
}
block.update(blockItem);
this.blockItems.put(blockItem.getBlockId(), block);
}
this.bornPos = new Position(arrangementInfo.getBornPos());
this.bornRot = new Position(arrangementInfo.getBornRot());
this.djinnPos = new Position(arrangementInfo.getDjinnPos());
this.homeBgmId = arrangementInfo.getBgmId();
this.mainHouse = HomeFurnitureItem.parseFrom(arrangementInfo.getMainHouse());
this.tmpVersion = arrangementInfo.getTmpVersion();
}
public int getRoomSceneId() {
if (mainHouse == null || mainHouse.getAsItem() == null) {
return 0;
}
return mainHouse.getAsItem().getRoomSceneId();
}
public int calComfort() {
return this.blockItems.values().stream().mapToInt(HomeBlockItem::calComfort).sum();
}
public HomeSceneArrangementInfo toProto() {
var proto = HomeSceneArrangementInfo.newBuilder();
blockItems.values().forEach(b -> proto.addBlockArrangementInfoList(b.toProto()));
proto
.setComfortValue(calComfort())
.setBornPos(bornPos.toProto())
.setBornRot(bornRot.toProto())
.setDjinnPos(djinnPos.toProto())
.setIsSetBornPos(true)
.setSceneId(sceneId)
.setBgmId(homeBgmId)
.setTmpVersion(tmpVersion);
if (mainHouse != null) {
proto.setMainHouse(mainHouse.toProto());
}
return proto.build();
}
}
package emu.grasscutter.game.home;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.binout.HomeworldDefaultSaveData;
import emu.grasscutter.net.proto.HomeSceneArrangementInfoOuterClass.HomeSceneArrangementInfo;
import emu.grasscutter.utils.Position;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@Builder(builderMethodName = "of")
@FieldDefaults(level = AccessLevel.PRIVATE)
public class HomeSceneItem {
@Id int sceneId;
Map<Integer, HomeBlockItem> blockItems;
Position bornPos;
Position bornRot;
Position djinnPos;
int homeBgmId;
HomeFurnitureItem mainHouse;
int tmpVersion;
public static HomeSceneItem parseFrom(HomeworldDefaultSaveData defaultItem, int sceneId) {
return HomeSceneItem.of()
.sceneId(sceneId)
.blockItems(
defaultItem.getHomeBlockLists().stream()
.map(HomeBlockItem::parseFrom)
.collect(Collectors.toMap(HomeBlockItem::getBlockId, y -> y)))
.bornPos(defaultItem.getBornPos())
.bornRot(defaultItem.getBornRot() == null ? new Position() : defaultItem.getBornRot())
.djinnPos(defaultItem.getDjinPos() == null ? new Position() : defaultItem.getDjinPos())
.mainHouse(
defaultItem.getMainhouse() == null
? null
: HomeFurnitureItem.parseFrom(defaultItem.getMainhouse()))
.build();
}
public void update(HomeSceneArrangementInfo arrangementInfo) {
for (var blockItem : arrangementInfo.getBlockArrangementInfoListList()) {
var block = this.blockItems.get(blockItem.getBlockId());
if (block == null) {
Grasscutter.getLogger().warn("Could not found the Home Block {}", blockItem.getBlockId());
continue;
}
block.update(blockItem);
this.blockItems.put(blockItem.getBlockId(), block);
}
this.bornPos = new Position(arrangementInfo.getBornPos());
this.bornRot = new Position(arrangementInfo.getBornRot());
this.djinnPos = new Position(arrangementInfo.getDjinnPos());
this.homeBgmId = arrangementInfo.getBgmId();
this.mainHouse = HomeFurnitureItem.parseFrom(arrangementInfo.getMainHouse());
this.tmpVersion = arrangementInfo.getTmpVersion();
}
public int getRoomSceneId() {
if (mainHouse == null || mainHouse.getAsItem() == null) {
return 0;
}
return mainHouse.getAsItem().getRoomSceneId();
}
public int calComfort() {
return this.blockItems.values().stream().mapToInt(HomeBlockItem::calComfort).sum();
}
public HomeSceneArrangementInfo toProto() {
var proto = HomeSceneArrangementInfo.newBuilder();
blockItems.values().forEach(b -> proto.addBlockArrangementInfoList(b.toProto()));
proto
.setComfortValue(calComfort())
.setBornPos(bornPos.toProto())
.setBornRot(bornRot.toProto())
.setDjinnPos(djinnPos.toProto())
.setIsSetBornPos(true)
.setSceneId(sceneId)
.setBgmId(homeBgmId)
.setTmpVersion(tmpVersion);
if (mainHouse != null) {
proto.setMainHouse(mainHouse.toProto());
}
return proto.build();
}
}

View File

@@ -1,39 +1,39 @@
package emu.grasscutter.game.inventory;
import java.util.HashSet;
import java.util.Set;
public class EquipInventoryTab implements InventoryTab {
private final Set<GameItem> items;
private final int maxCapacity;
public EquipInventoryTab(int maxCapacity) {
this.items = new HashSet<GameItem>();
this.maxCapacity = maxCapacity;
}
@Override
public GameItem getItemById(int id) {
return null;
}
@Override
public void onAddItem(GameItem item) {
this.items.add(item);
}
@Override
public void onRemoveItem(GameItem item) {
this.items.remove(item);
}
@Override
public int getSize() {
return this.items.size();
}
@Override
public int getMaxCapacity() {
return this.maxCapacity;
}
}
package emu.grasscutter.game.inventory;
import java.util.HashSet;
import java.util.Set;
public class EquipInventoryTab implements InventoryTab {
private final Set<GameItem> items;
private final int maxCapacity;
public EquipInventoryTab(int maxCapacity) {
this.items = new HashSet<GameItem>();
this.maxCapacity = maxCapacity;
}
@Override
public GameItem getItemById(int id) {
return null;
}
@Override
public void onAddItem(GameItem item) {
this.items.add(item);
}
@Override
public void onRemoveItem(GameItem item) {
this.items.remove(item);
}
@Override
public int getSize() {
return this.items.size();
}
@Override
public int getMaxCapacity() {
return this.maxCapacity;
}
}

View File

@@ -1,13 +1,13 @@
package emu.grasscutter.game.inventory;
public interface InventoryTab {
GameItem getItemById(int id);
void onAddItem(GameItem item);
void onRemoveItem(GameItem item);
int getSize();
int getMaxCapacity();
}
package emu.grasscutter.game.inventory;
public interface InventoryTab {
GameItem getItemById(int id);
void onAddItem(GameItem item);
void onRemoveItem(GameItem item);
int getSize();
int getMaxCapacity();
}

View File

@@ -1,27 +1,27 @@
package emu.grasscutter.game.inventory;
public class ItemDef {
private int itemId;
private int count;
public ItemDef(int itemId, int count) {
this.itemId = itemId;
this.count = count;
}
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
package emu.grasscutter.game.inventory;
public class ItemDef {
private int itemId;
private int count;
public ItemDef(int itemId, int count) {
this.itemId = itemId;
this.count = count;
}
public int getItemId() {
return itemId;
}
public void setItemId(int itemId) {
this.itemId = itemId;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}

View File

@@ -1,39 +1,39 @@
package emu.grasscutter.game.inventory;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class MaterialInventoryTab implements InventoryTab {
private final Int2ObjectMap<GameItem> items;
private final int maxCapacity;
public MaterialInventoryTab(int maxCapacity) {
this.items = new Int2ObjectOpenHashMap<>();
this.maxCapacity = maxCapacity;
}
@Override
public GameItem getItemById(int id) {
return this.items.get(id);
}
@Override
public void onAddItem(GameItem item) {
this.items.put(item.getItemId(), item);
}
@Override
public void onRemoveItem(GameItem item) {
this.items.remove(item.getItemId());
}
@Override
public int getSize() {
return this.items.size();
}
@Override
public int getMaxCapacity() {
return this.maxCapacity;
}
}
package emu.grasscutter.game.inventory;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class MaterialInventoryTab implements InventoryTab {
private final Int2ObjectMap<GameItem> items;
private final int maxCapacity;
public MaterialInventoryTab(int maxCapacity) {
this.items = new Int2ObjectOpenHashMap<>();
this.maxCapacity = maxCapacity;
}
@Override
public GameItem getItemById(int id) {
return this.items.get(id);
}
@Override
public void onAddItem(GameItem item) {
this.items.put(item.getItemId(), item);
}
@Override
public void onRemoveItem(GameItem item) {
this.items.remove(item.getItemId());
}
@Override
public int getSize() {
return this.items.size();
}
@Override
public int getMaxCapacity() {
return this.maxCapacity;
}
}

View File

@@ -1,111 +1,111 @@
package emu.grasscutter.game.mail;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.event.player.PlayerReceiveMailEvent;
import emu.grasscutter.server.packet.send.PacketDelMailRsp;
import emu.grasscutter.server.packet.send.PacketMailChangeNotify;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MailHandler extends BasePlayerManager {
private final List<Mail> mail;
public MailHandler(Player player) {
super(player);
this.mail = new ArrayList<>();
}
public List<Mail> getMail() {
return mail;
}
// ---------------------MAIL------------------------
public void sendMail(Mail message) {
// Call mail receive event.
PlayerReceiveMailEvent event = new PlayerReceiveMailEvent(this.getPlayer(), message);
event.call();
if (event.isCanceled()) return;
message = event.getMessage();
message.setOwnerUid(this.getPlayer().getUid());
message.save();
this.mail.add(message);
Grasscutter.getLogger()
.debug(
"Mail sent to user ["
+ this.getPlayer().getUid()
+ ":"
+ this.getPlayer().getNickname()
+ "]!");
if (this.getPlayer().isOnline()) {
this.getPlayer().sendPacket(new PacketMailChangeNotify(this.getPlayer(), message));
} // TODO: setup a way for the mail notification to show up when someone receives mail when they
// were offline
}
public boolean deleteMail(int mailId) {
Mail message = getMailById(mailId);
if (message != null) {
this.getMail().remove(mailId);
message.expireTime = 0;
message.save();
return true;
}
return false;
}
public void deleteMail(List<Integer> mailList) {
List<Integer> sortedMailList = new ArrayList<>();
sortedMailList.addAll(mailList);
Collections.sort(sortedMailList, Collections.reverseOrder());
List<Integer> deleted = new ArrayList<>();
for (int id : sortedMailList) {
if (this.deleteMail(id)) {
deleted.add(id);
}
}
player.getSession().send(new PacketDelMailRsp(player, deleted));
player.getSession().send(new PacketMailChangeNotify(player, null, deleted));
}
public Mail getMailById(int index) {
return this.mail.get(index);
}
public int getMailIndex(Mail message) {
return this.mail.indexOf(message);
}
public boolean replaceMailByIndex(int index, Mail message) {
if (getMailById(index) != null) {
this.mail.set(index, message);
message.save();
return true;
} else {
return false;
}
}
public void loadFromDatabase() {
List<Mail> mailList = DatabaseHelper.getAllMail(this.getPlayer());
for (Mail mail : mailList) {
this.getMail().add(mail);
}
}
}
package emu.grasscutter.game.mail;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.event.player.PlayerReceiveMailEvent;
import emu.grasscutter.server.packet.send.PacketDelMailRsp;
import emu.grasscutter.server.packet.send.PacketMailChangeNotify;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MailHandler extends BasePlayerManager {
private final List<Mail> mail;
public MailHandler(Player player) {
super(player);
this.mail = new ArrayList<>();
}
public List<Mail> getMail() {
return mail;
}
// ---------------------MAIL------------------------
public void sendMail(Mail message) {
// Call mail receive event.
PlayerReceiveMailEvent event = new PlayerReceiveMailEvent(this.getPlayer(), message);
event.call();
if (event.isCanceled()) return;
message = event.getMessage();
message.setOwnerUid(this.getPlayer().getUid());
message.save();
this.mail.add(message);
Grasscutter.getLogger()
.debug(
"Mail sent to user ["
+ this.getPlayer().getUid()
+ ":"
+ this.getPlayer().getNickname()
+ "]!");
if (this.getPlayer().isOnline()) {
this.getPlayer().sendPacket(new PacketMailChangeNotify(this.getPlayer(), message));
} // TODO: setup a way for the mail notification to show up when someone receives mail when they
// were offline
}
public boolean deleteMail(int mailId) {
Mail message = getMailById(mailId);
if (message != null) {
this.getMail().remove(mailId);
message.expireTime = 0;
message.save();
return true;
}
return false;
}
public void deleteMail(List<Integer> mailList) {
List<Integer> sortedMailList = new ArrayList<>();
sortedMailList.addAll(mailList);
Collections.sort(sortedMailList, Collections.reverseOrder());
List<Integer> deleted = new ArrayList<>();
for (int id : sortedMailList) {
if (this.deleteMail(id)) {
deleted.add(id);
}
}
player.getSession().send(new PacketDelMailRsp(player, deleted));
player.getSession().send(new PacketMailChangeNotify(player, null, deleted));
}
public Mail getMailById(int index) {
return this.mail.get(index);
}
public int getMailIndex(Mail message) {
return this.mail.indexOf(message);
}
public boolean replaceMailByIndex(int index, Mail message) {
if (getMailById(index) != null) {
this.mail.set(index, message);
message.save();
return true;
} else {
return false;
}
}
public void loadFromDatabase() {
List<Mail> mailList = DatabaseHelper.getAllMail(this.getPlayer());
for (Mail mail : mailList) {
this.getMail().add(mail);
}
}
}

View File

@@ -1,178 +1,178 @@
package emu.grasscutter.game.managers;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.home.FurnitureMakeSlotItem;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.ItemParamOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.Utils;
import java.util.ArrayList;
import java.util.List;
public class FurnitureManager extends BasePlayerManager {
public FurnitureManager(Player player) {
super(player);
}
public void onLogin() {
notifyUnlockFurniture();
notifyUnlockFurnitureSuite();
}
public void notifyUnlockFurniture() {
player
.getSession()
.send(new PacketUnlockedFurnitureFormulaDataNotify(player.getUnlockedFurniture()));
}
public void notifyUnlockFurnitureSuite() {
player
.getSession()
.send(new PacketUnlockedFurnitureSuiteDataNotify(player.getUnlockedFurnitureSuite()));
}
public boolean unlockFurnitureFormula(int id) {
if (!player.getUnlockedFurniture().add(id)) {
return false; // Already unlocked!
}
notifyUnlockFurniture();
return true;
}
public boolean unlockFurnitureSuite(int id) {
if (!player.getUnlockedFurnitureSuite().add(id)) {
return false; // Already unlocked!
}
notifyUnlockFurnitureSuite();
return true;
}
public void startMake(int makeId, int avatarId) {
var makeData = GameData.getFurnitureMakeConfigDataMap().get(makeId);
if (makeData == null) {
player
.getSession()
.send(
new PacketFurnitureMakeStartRsp(Retcode.RET_FURNITURE_MAKE_CONFIG_ERROR_VALUE, null));
return;
}
// check slot count
if (player.getHome().getLevelData().getFurnitureMakeSlotCount()
<= player.getHome().getFurnitureMakeSlotItemList().size()) {
player
.getSession()
.send(new PacketFurnitureMakeStartRsp(Retcode.RET_FURNITURE_MAKE_SLOT_FULL_VALUE, null));
return;
}
// pay items first
if (!player.getInventory().payItems(makeData.getMaterialItems())) {
player
.getSession()
.send(
new PacketFurnitureMakeStartRsp(
Retcode.RET_HOME_FURNITURE_COUNT_NOT_ENOUGH_VALUE, null));
return;
}
var furnitureSlot =
FurnitureMakeSlotItem.of()
.avatarId(avatarId)
.makeId(makeId)
.beginTime(Utils.getCurrentSeconds())
.durTime(makeData.getMakeTime())
.build();
// add furniture make task
player.getHome().getFurnitureMakeSlotItemList().add(furnitureSlot);
player
.getSession()
.send(
new PacketFurnitureMakeStartRsp(
Retcode.RET_SUCC_VALUE,
player.getHome().getFurnitureMakeSlotItemList().stream()
.map(FurnitureMakeSlotItem::toProto)
.toList()));
player.getHome().save();
}
public void queryStatus() {
if (player.getHome().getFurnitureMakeSlotItemList() == null) {
player.getHome().setFurnitureMakeSlotItemList(new ArrayList<>());
}
player.sendPacket(new PacketFurnitureMakeRsp(player.getHome()));
}
public void take(int index, int makeId, boolean isFastFinish) {
var makeData = GameData.getFurnitureMakeConfigDataMap().get(makeId);
if (makeData == null) {
player
.getSession()
.send(
new PacketTakeFurnitureMakeRsp(
Retcode.RET_FURNITURE_MAKE_CONFIG_ERROR_VALUE, makeId, null, null));
return;
}
var slotItem =
player.getHome().getFurnitureMakeSlotItemList().stream()
.filter(x -> x.getIndex() == index && x.getMakeId() == makeId)
.findFirst();
if (slotItem.isEmpty()) {
player
.getSession()
.send(
new PacketTakeFurnitureMakeRsp(
Retcode.RET_FURNITURE_MAKE_NO_MAKE_DATA_VALUE, makeId, null, null));
return;
}
// pay the speedup item
if (isFastFinish && !player.getInventory().payItem(107013, 1)) {
player
.getSession()
.send(
new PacketTakeFurnitureMakeRsp(
Retcode.RET_FURNITURE_MAKE_UNFINISH_VALUE, makeId, null, null));
return;
}
// check if player can take
// if (slotItem.get().getBeginTime() + slotItem.get().getDurTime() >=
// Utils.getCurrentSeconds() && !isFastFinish) {
// player.getSession().send(new
// PacketTakeFurnitureMakeRsp(Retcode.RET_FURNITURE_MAKE_UNFINISH_VALUE, makeId, null, null));
// return;
// }
player.getInventory().addItem(makeData.getFurnitureItemID(), makeData.getCount());
player.getHome().getFurnitureMakeSlotItemList().remove(slotItem.get());
// Should be for first craft, but until first craft check exists add exp for each item crafted
player.getInventory().addItem(121, makeData.getExp(), ActionReason.FurnitureMakeTake);
player
.getSession()
.send(
new PacketTakeFurnitureMakeRsp(
Retcode.RET_SUCC_VALUE,
makeId,
List.of(
ItemParamOuterClass.ItemParam.newBuilder()
.setItemId(makeData.getFurnitureItemID())
.setCount(makeData.getCount())
.build()),
player.getHome().getFurnitureMakeSlotItemList().stream()
.map(FurnitureMakeSlotItem::toProto)
.toList()));
player.getHome().save();
}
}
package emu.grasscutter.game.managers;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.home.FurnitureMakeSlotItem;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.ItemParamOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.Utils;
import java.util.ArrayList;
import java.util.List;
public class FurnitureManager extends BasePlayerManager {
public FurnitureManager(Player player) {
super(player);
}
public void onLogin() {
notifyUnlockFurniture();
notifyUnlockFurnitureSuite();
}
public void notifyUnlockFurniture() {
player
.getSession()
.send(new PacketUnlockedFurnitureFormulaDataNotify(player.getUnlockedFurniture()));
}
public void notifyUnlockFurnitureSuite() {
player
.getSession()
.send(new PacketUnlockedFurnitureSuiteDataNotify(player.getUnlockedFurnitureSuite()));
}
public boolean unlockFurnitureFormula(int id) {
if (!player.getUnlockedFurniture().add(id)) {
return false; // Already unlocked!
}
notifyUnlockFurniture();
return true;
}
public boolean unlockFurnitureSuite(int id) {
if (!player.getUnlockedFurnitureSuite().add(id)) {
return false; // Already unlocked!
}
notifyUnlockFurnitureSuite();
return true;
}
public void startMake(int makeId, int avatarId) {
var makeData = GameData.getFurnitureMakeConfigDataMap().get(makeId);
if (makeData == null) {
player
.getSession()
.send(
new PacketFurnitureMakeStartRsp(Retcode.RET_FURNITURE_MAKE_CONFIG_ERROR_VALUE, null));
return;
}
// check slot count
if (player.getHome().getLevelData().getFurnitureMakeSlotCount()
<= player.getHome().getFurnitureMakeSlotItemList().size()) {
player
.getSession()
.send(new PacketFurnitureMakeStartRsp(Retcode.RET_FURNITURE_MAKE_SLOT_FULL_VALUE, null));
return;
}
// pay items first
if (!player.getInventory().payItems(makeData.getMaterialItems())) {
player
.getSession()
.send(
new PacketFurnitureMakeStartRsp(
Retcode.RET_HOME_FURNITURE_COUNT_NOT_ENOUGH_VALUE, null));
return;
}
var furnitureSlot =
FurnitureMakeSlotItem.of()
.avatarId(avatarId)
.makeId(makeId)
.beginTime(Utils.getCurrentSeconds())
.durTime(makeData.getMakeTime())
.build();
// add furniture make task
player.getHome().getFurnitureMakeSlotItemList().add(furnitureSlot);
player
.getSession()
.send(
new PacketFurnitureMakeStartRsp(
Retcode.RET_SUCC_VALUE,
player.getHome().getFurnitureMakeSlotItemList().stream()
.map(FurnitureMakeSlotItem::toProto)
.toList()));
player.getHome().save();
}
public void queryStatus() {
if (player.getHome().getFurnitureMakeSlotItemList() == null) {
player.getHome().setFurnitureMakeSlotItemList(new ArrayList<>());
}
player.sendPacket(new PacketFurnitureMakeRsp(player.getHome()));
}
public void take(int index, int makeId, boolean isFastFinish) {
var makeData = GameData.getFurnitureMakeConfigDataMap().get(makeId);
if (makeData == null) {
player
.getSession()
.send(
new PacketTakeFurnitureMakeRsp(
Retcode.RET_FURNITURE_MAKE_CONFIG_ERROR_VALUE, makeId, null, null));
return;
}
var slotItem =
player.getHome().getFurnitureMakeSlotItemList().stream()
.filter(x -> x.getIndex() == index && x.getMakeId() == makeId)
.findFirst();
if (slotItem.isEmpty()) {
player
.getSession()
.send(
new PacketTakeFurnitureMakeRsp(
Retcode.RET_FURNITURE_MAKE_NO_MAKE_DATA_VALUE, makeId, null, null));
return;
}
// pay the speedup item
if (isFastFinish && !player.getInventory().payItem(107013, 1)) {
player
.getSession()
.send(
new PacketTakeFurnitureMakeRsp(
Retcode.RET_FURNITURE_MAKE_UNFINISH_VALUE, makeId, null, null));
return;
}
// check if player can take
// if (slotItem.get().getBeginTime() + slotItem.get().getDurTime() >=
// Utils.getCurrentSeconds() && !isFastFinish) {
// player.getSession().send(new
// PacketTakeFurnitureMakeRsp(Retcode.RET_FURNITURE_MAKE_UNFINISH_VALUE, makeId, null, null));
// return;
// }
player.getInventory().addItem(makeData.getFurnitureItemID(), makeData.getCount());
player.getHome().getFurnitureMakeSlotItemList().remove(slotItem.get());
// Should be for first craft, but until first craft check exists add exp for each item crafted
player.getInventory().addItem(121, makeData.getExp(), ActionReason.FurnitureMakeTake);
player
.getSession()
.send(
new PacketTakeFurnitureMakeRsp(
Retcode.RET_SUCC_VALUE,
makeId,
List.of(
ItemParamOuterClass.ItemParam.newBuilder()
.setItemId(makeData.getFurnitureItemID())
.setCount(makeData.getCount())
.build()),
player.getHome().getFurnitureMakeSlotItemList().stream()
.map(FurnitureMakeSlotItem::toProto)
.toList()));
player.getHome().save();
}
}

View File

@@ -1,115 +1,115 @@
package emu.grasscutter.game.managers;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.server.packet.send.PacketAvatarPropNotify;
import emu.grasscutter.server.packet.send.PacketAvatarSatiationDataNotify;
import emu.grasscutter.server.packet.send.PacketPlayerGameTimeNotify;
import emu.grasscutter.server.packet.send.PacketPlayerTimeNotify;
import java.util.HashMap;
import java.util.Map;
public class SatiationManager extends BasePlayerManager {
public SatiationManager(Player player) {
super(player);
}
/********************
* Change satiation
********************/
public synchronized boolean addSatiation(Avatar avatar, float satiationIncrease, int itemId) {
// Satiation is max 10000 but can go over in the case of overeating
Map<Integer, Long> propMap = new HashMap<>();
int satiation = Math.round(satiationIncrease * 100);
float totalSatiation = ((satiationIncrease * 100) + avatar.getSatiation());
// Update client time
updateTime();
// Calculate times
var playerTime = (player.getClientTime() / 1000);
float finishTime = playerTime + (totalSatiation / 30);
// Penalty
long penaltyTime = playerTime;
long penaltyValue = avatar.getSatiationPenalty();
if (totalSatiation + avatar.getSatiation() > 10000 && penaltyValue == 0) {
// Penalty is always 30sec
penaltyTime += 30;
penaltyValue = 3000;
}
// Add satiation
if (!addSatiationDirectly(avatar, satiation)) return false;
propMap.put(PlayerProperty.PROP_SATIATION_VAL.getId(), Long.valueOf(satiation));
propMap.put(PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), penaltyValue);
// Send packets
player.getSession().send(new PacketAvatarPropNotify(avatar, propMap));
player.getSession().send(new PacketAvatarSatiationDataNotify(avatar, finishTime, penaltyTime));
return true;
}
public synchronized boolean addSatiationDirectly(Avatar avatar, int value) {
if (!avatar.addSatiation(value)) return false;
// Update avatar
avatar.save();
return true;
}
public synchronized void removeSatiationDirectly(Avatar avatar, int value) {
avatar.reduceSatiation(value);
avatar.reduceSatiationPenalty(3000);
avatar.save();
// Update avatar to no satiation
updateSingleAvatar(avatar, 0);
}
public synchronized void reduceSatiation() {
/* Satiation may not reduce while paused on official but it will here */
// Get all avatars with satiation
player
.getAvatars()
.forEach(
avatar -> {
// Ensure avatar isn't stuck in penalty
if (avatar.getSatiationPenalty() > 0 && avatar.getSatiation() == 0) {
avatar.reduceSatiationPenalty(3000);
}
// Reduce satiation
if (avatar.getSatiation() > 0) {
// Reduce penalty first
if (avatar.getSatiationPenalty() > 0) {
// Penalty reduction rate is 1/s
avatar.reduceSatiationPenalty(100);
} else {
// Satiation reduction rate is 0.3/s
avatar.reduceSatiation(30);
// Update all packets every tick else it won't work
// Surely there is a better way to handle this
addSatiation(avatar, 0, 0);
}
}
});
}
/********************
* Player Updates
********************/
public synchronized void updateSingleAvatar(Avatar avatar, float givenTime) {
float time = (player.getClientTime() / 1000) + givenTime;
player.getSession().send(new PacketAvatarPropNotify(avatar));
player.getSession().send(new PacketAvatarSatiationDataNotify(time, avatar));
}
private void updateTime() {
player.getSession().send(new PacketPlayerGameTimeNotify(player));
player.getSession().send(new PacketPlayerTimeNotify(player));
}
}
package emu.grasscutter.game.managers;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.server.packet.send.PacketAvatarPropNotify;
import emu.grasscutter.server.packet.send.PacketAvatarSatiationDataNotify;
import emu.grasscutter.server.packet.send.PacketPlayerGameTimeNotify;
import emu.grasscutter.server.packet.send.PacketPlayerTimeNotify;
import java.util.HashMap;
import java.util.Map;
public class SatiationManager extends BasePlayerManager {
public SatiationManager(Player player) {
super(player);
}
/********************
* Change satiation
********************/
public synchronized boolean addSatiation(Avatar avatar, float satiationIncrease, int itemId) {
// Satiation is max 10000 but can go over in the case of overeating
Map<Integer, Long> propMap = new HashMap<>();
int satiation = Math.round(satiationIncrease * 100);
float totalSatiation = ((satiationIncrease * 100) + avatar.getSatiation());
// Update client time
updateTime();
// Calculate times
var playerTime = (player.getClientTime() / 1000);
float finishTime = playerTime + (totalSatiation / 30);
// Penalty
long penaltyTime = playerTime;
long penaltyValue = avatar.getSatiationPenalty();
if (totalSatiation + avatar.getSatiation() > 10000 && penaltyValue == 0) {
// Penalty is always 30sec
penaltyTime += 30;
penaltyValue = 3000;
}
// Add satiation
if (!addSatiationDirectly(avatar, satiation)) return false;
propMap.put(PlayerProperty.PROP_SATIATION_VAL.getId(), Long.valueOf(satiation));
propMap.put(PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), penaltyValue);
// Send packets
player.getSession().send(new PacketAvatarPropNotify(avatar, propMap));
player.getSession().send(new PacketAvatarSatiationDataNotify(avatar, finishTime, penaltyTime));
return true;
}
public synchronized boolean addSatiationDirectly(Avatar avatar, int value) {
if (!avatar.addSatiation(value)) return false;
// Update avatar
avatar.save();
return true;
}
public synchronized void removeSatiationDirectly(Avatar avatar, int value) {
avatar.reduceSatiation(value);
avatar.reduceSatiationPenalty(3000);
avatar.save();
// Update avatar to no satiation
updateSingleAvatar(avatar, 0);
}
public synchronized void reduceSatiation() {
/* Satiation may not reduce while paused on official but it will here */
// Get all avatars with satiation
player
.getAvatars()
.forEach(
avatar -> {
// Ensure avatar isn't stuck in penalty
if (avatar.getSatiationPenalty() > 0 && avatar.getSatiation() == 0) {
avatar.reduceSatiationPenalty(3000);
}
// Reduce satiation
if (avatar.getSatiation() > 0) {
// Reduce penalty first
if (avatar.getSatiationPenalty() > 0) {
// Penalty reduction rate is 1/s
avatar.reduceSatiationPenalty(100);
} else {
// Satiation reduction rate is 0.3/s
avatar.reduceSatiation(30);
// Update all packets every tick else it won't work
// Surely there is a better way to handle this
addSatiation(avatar, 0, 0);
}
}
});
}
/********************
* Player Updates
********************/
public synchronized void updateSingleAvatar(Avatar avatar, float givenTime) {
float time = (player.getClientTime() / 1000) + givenTime;
player.getSession().send(new PacketAvatarPropNotify(avatar));
player.getSession().send(new PacketAvatarSatiationDataNotify(time, avatar));
}
private void updateTime() {
player.getSession().send(new PacketPlayerGameTimeNotify(player));
player.getSession().send(new PacketPlayerTimeNotify(player));
}
}

View File

@@ -1,211 +1,211 @@
package emu.grasscutter.game.managers;
import ch.qos.logback.classic.Logger;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
// Statue of the Seven Manager
public class SotSManager extends BasePlayerManager {
// NOTE: Spring volume balance *1 = fight prop HP *100
public static final int GlobalMaximumSpringVolume =
PlayerProperty.PROP_MAX_SPRING_VOLUME.getMax();
private final Logger logger = Grasscutter.getLogger();
private final boolean enablePriorityHealing = false;
private Timer autoRecoverTimer;
public SotSManager(Player player) {
super(player);
}
public boolean getIsAutoRecoveryEnabled() {
return player.getProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE) == 1;
}
public void setIsAutoRecoveryEnabled(boolean enabled) {
player.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, enabled ? 1 : 0);
player.save();
}
public int getAutoRecoveryPercentage() {
return player.getProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT);
}
public void setAutoRecoveryPercentage(int percentage) {
player.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, percentage);
player.save();
}
public long getLastUsed() {
return player.getSpringLastUsed();
}
public void setLastUsed() {
player.setSpringLastUsed(System.currentTimeMillis() / 1000);
player.save();
}
public int getMaxVolume() {
return player.getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME);
}
public void setMaxVolume(int volume) {
player.setProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME, volume);
player.save();
}
public int getCurrentVolume() {
return player.getProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME);
}
public void setCurrentVolume(int volume) {
player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, volume);
setLastUsed();
player.save();
}
public void handleEnterTransPointRegionNotify() {
logger.trace("Player entered statue region");
autoRevive();
if (autoRecoverTimer == null) {
autoRecoverTimer = new Timer();
autoRecoverTimer.schedule(new AutoRecoverTimerTick(), 2500, 15000);
}
}
public void handleExitTransPointRegionNotify() {
logger.trace("Player left statue region");
if (autoRecoverTimer != null) {
autoRecoverTimer.cancel();
autoRecoverTimer = null;
}
}
// autoRevive automatically revives all team members.
public void autoRevive() {
player
.getTeamManager()
.getActiveTeam()
.forEach(
entity -> {
boolean isAlive = entity.isAlive();
if (isAlive) {
return;
}
logger.trace("Reviving avatar " + entity.getAvatar().getAvatarData().getName());
player.getTeamManager().reviveAvatar(entity.getAvatar());
player.getTeamManager().healAvatar(entity.getAvatar(), 30, 0);
});
}
public void checkAndHealAvatar(EntityAvatar entity) {
int maxHP = (int) (entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * 100);
int currentHP = (int) (entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) * 100);
if (currentHP == maxHP) {
return;
}
int targetHP = maxHP * getAutoRecoveryPercentage() / 100;
if (targetHP > currentHP) {
int needHP = targetHP - currentHP;
int currentVolume = getCurrentVolume();
if (currentVolume >= needHP) {
// sufficient
setCurrentVolume(currentVolume - needHP);
} else {
// insufficient balance
needHP = currentVolume;
setCurrentVolume(0);
}
if (needHP > 0) {
logger.trace(
"Healing avatar " + entity.getAvatar().getAvatarData().getName() + " +" + needHP);
player.getTeamManager().healAvatar(entity.getAvatar(), 0, needHP);
player
.getSession()
.send(
new PacketEntityFightPropChangeReasonNotify(
entity,
FightProperty.FIGHT_PROP_CUR_HP,
((float) needHP / 100),
List.of(3),
PropChangeReason.PROP_CHANGE_REASON_STATUE_RECOVER,
ChangeHpReason.CHANGE_HP_REASON_ADD_STATUE));
player
.getSession()
.send(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
}
}
}
public void refillSpringVolume() {
// Temporary: Max spring volume depends on level of the statues in Mondstadt and Liyue. Override
// until we have statue level.
// TODO: remove
// https://genshin-impact.fandom.com/wiki/Statue_of_The_Seven#:~:text=region%20of%20Inazuma.-,Statue%20Levels,-Upon%20first%20unlocking
setMaxVolume(8500000);
// Temporary: Auto enable 100% statue recovery until we can adjust statue settings in game
// TODO: remove
setAutoRecoveryPercentage(100);
setIsAutoRecoveryEnabled(true);
int maxVolume = getMaxVolume();
int currentVolume = getCurrentVolume();
if (currentVolume < maxVolume) {
long now = System.currentTimeMillis() / 1000;
int secondsSinceLastUsed = (int) (now - getLastUsed());
// 15s = 1% max volume
int volumeRefilled = secondsSinceLastUsed * maxVolume / 15 / 100;
logger.trace("Statue has refilled HP volume: " + volumeRefilled);
currentVolume = Math.min(currentVolume + volumeRefilled, maxVolume);
logger.trace("Statue remaining HP volume: " + currentVolume);
setCurrentVolume(currentVolume);
}
}
private class AutoRecoverTimerTick extends TimerTask {
// autoRecover checks player setting to see if auto recover is enabled, and refill HP to the
// predefined level.
public void run() {
refillSpringVolume();
logger.trace(
"isAutoRecoveryEnabled: "
+ getIsAutoRecoveryEnabled()
+ "\tautoRecoverPercentage: "
+ getAutoRecoveryPercentage());
if (getIsAutoRecoveryEnabled()) {
List<EntityAvatar> activeTeam = player.getTeamManager().getActiveTeam();
// When the statue does not have enough remaining volume:
// Enhanced experience: Enable priority healing
// The current active character will get healed first, then
// sequential.
// Vanilla experience: Disable priority healing
// Sequential healing based on character index.
int priorityIndex =
enablePriorityHealing ? player.getTeamManager().getCurrentCharacterIndex() : -1;
if (priorityIndex >= 0) {
checkAndHealAvatar(activeTeam.get(priorityIndex));
}
for (int i = 0; i < activeTeam.size(); i++) {
if (i != priorityIndex) {
checkAndHealAvatar(activeTeam.get(i));
}
}
}
}
}
}
package emu.grasscutter.game.managers;
import ch.qos.logback.classic.Logger;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
// Statue of the Seven Manager
public class SotSManager extends BasePlayerManager {
// NOTE: Spring volume balance *1 = fight prop HP *100
public static final int GlobalMaximumSpringVolume =
PlayerProperty.PROP_MAX_SPRING_VOLUME.getMax();
private final Logger logger = Grasscutter.getLogger();
private final boolean enablePriorityHealing = false;
private Timer autoRecoverTimer;
public SotSManager(Player player) {
super(player);
}
public boolean getIsAutoRecoveryEnabled() {
return player.getProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE) == 1;
}
public void setIsAutoRecoveryEnabled(boolean enabled) {
player.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, enabled ? 1 : 0);
player.save();
}
public int getAutoRecoveryPercentage() {
return player.getProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT);
}
public void setAutoRecoveryPercentage(int percentage) {
player.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, percentage);
player.save();
}
public long getLastUsed() {
return player.getSpringLastUsed();
}
public void setLastUsed() {
player.setSpringLastUsed(System.currentTimeMillis() / 1000);
player.save();
}
public int getMaxVolume() {
return player.getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME);
}
public void setMaxVolume(int volume) {
player.setProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME, volume);
player.save();
}
public int getCurrentVolume() {
return player.getProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME);
}
public void setCurrentVolume(int volume) {
player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, volume);
setLastUsed();
player.save();
}
public void handleEnterTransPointRegionNotify() {
logger.trace("Player entered statue region");
autoRevive();
if (autoRecoverTimer == null) {
autoRecoverTimer = new Timer();
autoRecoverTimer.schedule(new AutoRecoverTimerTick(), 2500, 15000);
}
}
public void handleExitTransPointRegionNotify() {
logger.trace("Player left statue region");
if (autoRecoverTimer != null) {
autoRecoverTimer.cancel();
autoRecoverTimer = null;
}
}
// autoRevive automatically revives all team members.
public void autoRevive() {
player
.getTeamManager()
.getActiveTeam()
.forEach(
entity -> {
boolean isAlive = entity.isAlive();
if (isAlive) {
return;
}
logger.trace("Reviving avatar " + entity.getAvatar().getAvatarData().getName());
player.getTeamManager().reviveAvatar(entity.getAvatar());
player.getTeamManager().healAvatar(entity.getAvatar(), 30, 0);
});
}
public void checkAndHealAvatar(EntityAvatar entity) {
int maxHP = (int) (entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * 100);
int currentHP = (int) (entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) * 100);
if (currentHP == maxHP) {
return;
}
int targetHP = maxHP * getAutoRecoveryPercentage() / 100;
if (targetHP > currentHP) {
int needHP = targetHP - currentHP;
int currentVolume = getCurrentVolume();
if (currentVolume >= needHP) {
// sufficient
setCurrentVolume(currentVolume - needHP);
} else {
// insufficient balance
needHP = currentVolume;
setCurrentVolume(0);
}
if (needHP > 0) {
logger.trace(
"Healing avatar " + entity.getAvatar().getAvatarData().getName() + " +" + needHP);
player.getTeamManager().healAvatar(entity.getAvatar(), 0, needHP);
player
.getSession()
.send(
new PacketEntityFightPropChangeReasonNotify(
entity,
FightProperty.FIGHT_PROP_CUR_HP,
((float) needHP / 100),
List.of(3),
PropChangeReason.PROP_CHANGE_REASON_STATUE_RECOVER,
ChangeHpReason.CHANGE_HP_REASON_ADD_STATUE));
player
.getSession()
.send(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP));
}
}
}
public void refillSpringVolume() {
// Temporary: Max spring volume depends on level of the statues in Mondstadt and Liyue. Override
// until we have statue level.
// TODO: remove
// https://genshin-impact.fandom.com/wiki/Statue_of_The_Seven#:~:text=region%20of%20Inazuma.-,Statue%20Levels,-Upon%20first%20unlocking
setMaxVolume(8500000);
// Temporary: Auto enable 100% statue recovery until we can adjust statue settings in game
// TODO: remove
setAutoRecoveryPercentage(100);
setIsAutoRecoveryEnabled(true);
int maxVolume = getMaxVolume();
int currentVolume = getCurrentVolume();
if (currentVolume < maxVolume) {
long now = System.currentTimeMillis() / 1000;
int secondsSinceLastUsed = (int) (now - getLastUsed());
// 15s = 1% max volume
int volumeRefilled = secondsSinceLastUsed * maxVolume / 15 / 100;
logger.trace("Statue has refilled HP volume: " + volumeRefilled);
currentVolume = Math.min(currentVolume + volumeRefilled, maxVolume);
logger.trace("Statue remaining HP volume: " + currentVolume);
setCurrentVolume(currentVolume);
}
}
private class AutoRecoverTimerTick extends TimerTask {
// autoRecover checks player setting to see if auto recover is enabled, and refill HP to the
// predefined level.
public void run() {
refillSpringVolume();
logger.trace(
"isAutoRecoveryEnabled: "
+ getIsAutoRecoveryEnabled()
+ "\tautoRecoverPercentage: "
+ getAutoRecoveryPercentage());
if (getIsAutoRecoveryEnabled()) {
List<EntityAvatar> activeTeam = player.getTeamManager().getActiveTeam();
// When the statue does not have enough remaining volume:
// Enhanced experience: Enable priority healing
// The current active character will get healed first, then
// sequential.
// Vanilla experience: Disable priority healing
// Sequential healing based on character index.
int priorityIndex =
enablePriorityHealing ? player.getTeamManager().getCurrentCharacterIndex() : -1;
if (priorityIndex >= 0) {
checkAndHealAvatar(activeTeam.get(priorityIndex));
}
for (int i = 0; i < activeTeam.size(); i++) {
if (i != priorityIndex) {
checkAndHealAvatar(activeTeam.get(i));
}
}
}
}
}
}

View File

@@ -1,12 +1,12 @@
package emu.grasscutter.game.managers.blossom;
import java.util.List;
import java.util.Map;
import lombok.Getter;
public class BlossomConfig {
@Getter private int monsterFightingVolume;
// @Getter private Int2ObjectMap<IntList> monsterIdsPerDifficulty; // Need to wrangle Gson for
// this
@Getter private Map<Integer, List<Integer>> monsterIdsPerDifficulty;
}
package emu.grasscutter.game.managers.blossom;
import java.util.List;
import java.util.Map;
import lombok.Getter;
public class BlossomConfig {
@Getter private int monsterFightingVolume;
// @Getter private Int2ObjectMap<IntList> monsterIdsPerDifficulty; // Need to wrangle Gson for
// this
@Getter private Map<Integer, List<Integer>> monsterIdsPerDifficulty;
}

View File

@@ -1,35 +1,35 @@
package emu.grasscutter.game.managers.blossom;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.Getter;
public enum BlossomType {
GOLD(70360056, 101001001, 1),
BLUE(70360057, 101002003, 2);
private static final Int2ObjectMap<BlossomType> map =
new Int2ObjectOpenHashMap<>(
Stream.of(values()).collect(Collectors.toMap(x -> x.getGadgetId(), x -> x)));
@Getter private final int gadgetId;
@Getter private final int circleCampId;
@Getter private final int blossomChestId;
BlossomType(int gadgetId, int circleCampId, int blossomChestId) {
this.gadgetId = gadgetId;
this.circleCampId = circleCampId;
this.blossomChestId = blossomChestId;
}
public static BlossomType valueOf(int i) {
return map.get(i);
}
public static BlossomType random() {
BlossomType[] values = values();
return values[Utils.randomRange(0, values.length)];
}
}
package emu.grasscutter.game.managers.blossom;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.Getter;
public enum BlossomType {
GOLD(70360056, 101001001, 1),
BLUE(70360057, 101002003, 2);
private static final Int2ObjectMap<BlossomType> map =
new Int2ObjectOpenHashMap<>(
Stream.of(values()).collect(Collectors.toMap(x -> x.getGadgetId(), x -> x)));
@Getter private final int gadgetId;
@Getter private final int circleCampId;
@Getter private final int blossomChestId;
BlossomType(int gadgetId, int circleCampId, int blossomChestId) {
this.gadgetId = gadgetId;
this.circleCampId = circleCampId;
this.blossomChestId = blossomChestId;
}
public static BlossomType valueOf(int i) {
return map.get(i);
}
public static BlossomType random() {
BlossomType[] values = values();
return values[Utils.randomRange(0, values.length)];
}
}

View File

@@ -1,53 +1,53 @@
package emu.grasscutter.game.managers.cooking;
import dev.morphia.annotations.Entity;
import lombok.Getter;
@Entity
public class ActiveCookCompoundData {
private final int costTime;
@Getter private final int compoundId;
@Getter private int totalCount;
private int startTime;
public ActiveCookCompoundData(int compoundId, int processTime, int count, int startTime) {
this.compoundId = compoundId;
this.costTime = processTime;
this.totalCount = count;
this.startTime = startTime;
}
public int getOutputCount(int currentTime) {
int cnt = (currentTime - startTime) / costTime;
if (cnt > totalCount) return totalCount;
else return cnt;
}
public int getWaitCount(int currentTime) {
return totalCount - getOutputCount(currentTime);
}
/** Get the timestamp of next output. If all finished,return 0 */
public int getOutputTime(int currentTime) {
int cnt = getOutputCount(currentTime);
if (cnt == totalCount) return 0;
else return startTime + (cnt + 1) * costTime;
}
public void addCompound(int count, int currentTime) {
if (getOutputCount(currentTime) == totalCount) startTime = currentTime - totalCount * costTime;
totalCount += count;
}
/**
* Take away all finished compound.
*
* @return The number of finished items.
*/
public int takeCompound(int currentTime) {
int count = getOutputCount(currentTime);
startTime += costTime * count;
totalCount -= count;
return count;
}
}
package emu.grasscutter.game.managers.cooking;
import dev.morphia.annotations.Entity;
import lombok.Getter;
@Entity
public class ActiveCookCompoundData {
private final int costTime;
@Getter private final int compoundId;
@Getter private int totalCount;
private int startTime;
public ActiveCookCompoundData(int compoundId, int processTime, int count, int startTime) {
this.compoundId = compoundId;
this.costTime = processTime;
this.totalCount = count;
this.startTime = startTime;
}
public int getOutputCount(int currentTime) {
int cnt = (currentTime - startTime) / costTime;
if (cnt > totalCount) return totalCount;
else return cnt;
}
public int getWaitCount(int currentTime) {
return totalCount - getOutputCount(currentTime);
}
/** Get the timestamp of next output. If all finished,return 0 */
public int getOutputTime(int currentTime) {
int cnt = getOutputCount(currentTime);
if (cnt == totalCount) return 0;
else return startTime + (cnt + 1) * costTime;
}
public void addCompound(int count, int currentTime) {
if (getOutputCount(currentTime) == totalCount) startTime = currentTime - totalCount * costTime;
totalCount += count;
}
/**
* Take away all finished compound.
*
* @return The number of finished items.
*/
public int takeCompound(int currentTime) {
int count = getOutputCount(currentTime);
startTime += costTime * count;
totalCount -= count;
return count;
}
}

View File

@@ -1,185 +1,185 @@
package emu.grasscutter.game.managers.cooking;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.CookRecipeDataOuterClass;
import emu.grasscutter.net.proto.PlayerCookArgsReqOuterClass.PlayerCookArgsReq;
import emu.grasscutter.net.proto.PlayerCookReqOuterClass.PlayerCookReq;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.packet.send.PacketCookDataNotify;
import emu.grasscutter.server.packet.send.PacketCookRecipeDataNotify;
import emu.grasscutter.server.packet.send.PacketPlayerCookArgsRsp;
import emu.grasscutter.server.packet.send.PacketPlayerCookRsp;
import io.netty.util.internal.ThreadLocalRandom;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class CookingManager extends BasePlayerManager {
private static final int MANUAL_PERFECT_COOK_QUALITY = 3;
private static Set<Integer> defaultUnlockedRecipies;
public CookingManager(Player player) {
super(player);
}
public static void initialize() {
// Initialize the set of recipies that are unlocked by default.
defaultUnlockedRecipies = new HashSet<>();
for (var recipe : GameData.getCookRecipeDataMap().values()) {
if (recipe.isDefaultUnlocked()) {
defaultUnlockedRecipies.add(recipe.getId());
}
}
}
/********************
* Unlocking for recipies.
********************/
public boolean unlockRecipe(int id) {
if (this.player.getUnlockedRecipies().containsKey(id)) {
return false; // Recipe already unlocked
}
// Tell the client that this blueprint is now unlocked and add the unlocked item to the player.
this.player.getUnlockedRecipies().put(id, 0);
this.player.sendPacket(new PacketCookRecipeDataNotify(id));
return true;
}
/********************
* Perform cooking.
********************/
private double getSpecialtyChance(ItemData cookedItem) {
// Chances taken from the Wiki.
return switch (cookedItem.getRankLevel()) {
case 1 -> 0.25;
case 2 -> 0.2;
case 3 -> 0.15;
default -> 0;
};
}
public void handlePlayerCookReq(PlayerCookReq req) {
// Get info from the request.
int recipeId = req.getRecipeId();
int quality = req.getQteQuality();
int count = req.getCookCount();
int avatar = req.getAssistAvatar();
// Get recipe data.
var recipeData = GameData.getCookRecipeDataMap().get(recipeId);
if (recipeData == null) {
this.player.sendPacket(new PacketPlayerCookRsp(Retcode.RET_FAIL));
return;
}
// Get proficiency for player.
int proficiency = this.player.getUnlockedRecipies().getOrDefault(recipeId, 0);
// Try consuming materials.
boolean success =
player.getInventory().payItems(recipeData.getInputVec(), count, ActionReason.Cook);
if (!success) {
this.player.sendPacket(new PacketPlayerCookRsp(Retcode.RET_FAIL));
}
// Get result item information.
int qualityIndex = quality == 0 ? 2 : quality - 1;
ItemParamData resultParam = recipeData.getQualityOutputVec().get(qualityIndex);
ItemData resultItemData = GameData.getItemDataMap().get(resultParam.getItemId());
// Handle character's specialties.
int specialtyCount = 0;
double specialtyChance = this.getSpecialtyChance(resultItemData);
var bonusData = GameData.getCookBonusDataMap().get(avatar);
if (bonusData != null && recipeId == bonusData.getRecipeId()) {
// Roll for specialy replacements.
for (int i = 0; i < count; i++) {
if (ThreadLocalRandom.current().nextDouble() <= specialtyChance) {
specialtyCount++;
}
}
}
// Obtain results.
List<GameItem> cookResults = new ArrayList<>();
int normalCount = count - specialtyCount;
GameItem cookResultNormal = new GameItem(resultItemData, resultParam.getCount() * normalCount);
cookResults.add(cookResultNormal);
this.player.getInventory().addItem(cookResultNormal);
if (specialtyCount > 0) {
ItemData specialtyItemData = GameData.getItemDataMap().get(bonusData.getReplacementItemId());
GameItem cookResultSpecialty =
new GameItem(specialtyItemData, resultParam.getCount() * specialtyCount);
cookResults.add(cookResultSpecialty);
this.player.getInventory().addItem(cookResultSpecialty);
}
// Increase player proficiency, if this was a manual perfect cook.
if (quality == MANUAL_PERFECT_COOK_QUALITY) {
proficiency = Math.min(proficiency + 1, recipeData.getMaxProficiency());
this.player.getUnlockedRecipies().put(recipeId, proficiency);
}
// Send response.
this.player.sendPacket(
new PacketPlayerCookRsp(cookResults, quality, count, recipeId, proficiency));
}
/********************
* Cooking arguments.
********************/
public void handleCookArgsReq(PlayerCookArgsReq req) {
this.player.sendPacket(new PacketPlayerCookArgsRsp());
}
/********************
* Notify unlocked recipies.
********************/
private void addDefaultUnlocked() {
// Get recipies that are already unlocked.
var unlockedRecipies = this.player.getUnlockedRecipies();
// Get recipies that should be unlocked by default but aren't.
var additionalRecipies = new HashSet<>(defaultUnlockedRecipies);
additionalRecipies.removeAll(unlockedRecipies.keySet());
// Add them to the player.
for (int id : additionalRecipies) {
unlockedRecipies.put(id, 0);
}
}
public void sendCookDataNotify() {
// Default unlocked recipes to player if they don't have them yet.
this.addDefaultUnlocked();
// Get unlocked recipes.
var unlockedRecipes = this.player.getUnlockedRecipies();
// Construct CookRecipeData protos.
List<CookRecipeDataOuterClass.CookRecipeData> data = new ArrayList<>();
unlockedRecipes.forEach(
(recipeId, proficiency) ->
data.add(
CookRecipeDataOuterClass.CookRecipeData.newBuilder()
.setRecipeId(recipeId)
.setProficiency(proficiency)
.build()));
// Send packet.
this.player.sendPacket(new PacketCookDataNotify(data));
}
}
package emu.grasscutter.game.managers.cooking;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.CookRecipeDataOuterClass;
import emu.grasscutter.net.proto.PlayerCookArgsReqOuterClass.PlayerCookArgsReq;
import emu.grasscutter.net.proto.PlayerCookReqOuterClass.PlayerCookReq;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.packet.send.PacketCookDataNotify;
import emu.grasscutter.server.packet.send.PacketCookRecipeDataNotify;
import emu.grasscutter.server.packet.send.PacketPlayerCookArgsRsp;
import emu.grasscutter.server.packet.send.PacketPlayerCookRsp;
import io.netty.util.internal.ThreadLocalRandom;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class CookingManager extends BasePlayerManager {
private static final int MANUAL_PERFECT_COOK_QUALITY = 3;
private static Set<Integer> defaultUnlockedRecipies;
public CookingManager(Player player) {
super(player);
}
public static void initialize() {
// Initialize the set of recipies that are unlocked by default.
defaultUnlockedRecipies = new HashSet<>();
for (var recipe : GameData.getCookRecipeDataMap().values()) {
if (recipe.isDefaultUnlocked()) {
defaultUnlockedRecipies.add(recipe.getId());
}
}
}
/********************
* Unlocking for recipies.
********************/
public boolean unlockRecipe(int id) {
if (this.player.getUnlockedRecipies().containsKey(id)) {
return false; // Recipe already unlocked
}
// Tell the client that this blueprint is now unlocked and add the unlocked item to the player.
this.player.getUnlockedRecipies().put(id, 0);
this.player.sendPacket(new PacketCookRecipeDataNotify(id));
return true;
}
/********************
* Perform cooking.
********************/
private double getSpecialtyChance(ItemData cookedItem) {
// Chances taken from the Wiki.
return switch (cookedItem.getRankLevel()) {
case 1 -> 0.25;
case 2 -> 0.2;
case 3 -> 0.15;
default -> 0;
};
}
public void handlePlayerCookReq(PlayerCookReq req) {
// Get info from the request.
int recipeId = req.getRecipeId();
int quality = req.getQteQuality();
int count = req.getCookCount();
int avatar = req.getAssistAvatar();
// Get recipe data.
var recipeData = GameData.getCookRecipeDataMap().get(recipeId);
if (recipeData == null) {
this.player.sendPacket(new PacketPlayerCookRsp(Retcode.RET_FAIL));
return;
}
// Get proficiency for player.
int proficiency = this.player.getUnlockedRecipies().getOrDefault(recipeId, 0);
// Try consuming materials.
boolean success =
player.getInventory().payItems(recipeData.getInputVec(), count, ActionReason.Cook);
if (!success) {
this.player.sendPacket(new PacketPlayerCookRsp(Retcode.RET_FAIL));
}
// Get result item information.
int qualityIndex = quality == 0 ? 2 : quality - 1;
ItemParamData resultParam = recipeData.getQualityOutputVec().get(qualityIndex);
ItemData resultItemData = GameData.getItemDataMap().get(resultParam.getItemId());
// Handle character's specialties.
int specialtyCount = 0;
double specialtyChance = this.getSpecialtyChance(resultItemData);
var bonusData = GameData.getCookBonusDataMap().get(avatar);
if (bonusData != null && recipeId == bonusData.getRecipeId()) {
// Roll for specialy replacements.
for (int i = 0; i < count; i++) {
if (ThreadLocalRandom.current().nextDouble() <= specialtyChance) {
specialtyCount++;
}
}
}
// Obtain results.
List<GameItem> cookResults = new ArrayList<>();
int normalCount = count - specialtyCount;
GameItem cookResultNormal = new GameItem(resultItemData, resultParam.getCount() * normalCount);
cookResults.add(cookResultNormal);
this.player.getInventory().addItem(cookResultNormal);
if (specialtyCount > 0) {
ItemData specialtyItemData = GameData.getItemDataMap().get(bonusData.getReplacementItemId());
GameItem cookResultSpecialty =
new GameItem(specialtyItemData, resultParam.getCount() * specialtyCount);
cookResults.add(cookResultSpecialty);
this.player.getInventory().addItem(cookResultSpecialty);
}
// Increase player proficiency, if this was a manual perfect cook.
if (quality == MANUAL_PERFECT_COOK_QUALITY) {
proficiency = Math.min(proficiency + 1, recipeData.getMaxProficiency());
this.player.getUnlockedRecipies().put(recipeId, proficiency);
}
// Send response.
this.player.sendPacket(
new PacketPlayerCookRsp(cookResults, quality, count, recipeId, proficiency));
}
/********************
* Cooking arguments.
********************/
public void handleCookArgsReq(PlayerCookArgsReq req) {
this.player.sendPacket(new PacketPlayerCookArgsRsp());
}
/********************
* Notify unlocked recipies.
********************/
private void addDefaultUnlocked() {
// Get recipies that are already unlocked.
var unlockedRecipies = this.player.getUnlockedRecipies();
// Get recipies that should be unlocked by default but aren't.
var additionalRecipies = new HashSet<>(defaultUnlockedRecipies);
additionalRecipies.removeAll(unlockedRecipies.keySet());
// Add them to the player.
for (int id : additionalRecipies) {
unlockedRecipies.put(id, 0);
}
}
public void sendCookDataNotify() {
// Default unlocked recipes to player if they don't have them yet.
this.addDefaultUnlocked();
// Get unlocked recipes.
var unlockedRecipes = this.player.getUnlockedRecipies();
// Construct CookRecipeData protos.
List<CookRecipeDataOuterClass.CookRecipeData> data = new ArrayList<>();
unlockedRecipes.forEach(
(recipeId, proficiency) ->
data.add(
CookRecipeDataOuterClass.CookRecipeData.newBuilder()
.setRecipeId(recipeId)
.setProficiency(proficiency)
.build()));
// Send packet.
this.player.sendPacket(new PacketCookDataNotify(data));
}
}

View File

@@ -1,101 +1,101 @@
package emu.grasscutter.game.managers.deforestation;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.HitTreeNotifyOuterClass;
import emu.grasscutter.net.proto.VectorOuterClass;
import emu.grasscutter.utils.Position;
import java.util.ArrayList;
import java.util.HashMap;
public class DeforestationManager extends BasePlayerManager {
static final int RECORD_EXPIRED_SECONDS = 60 * 5; // 5 min
static final int RECORD_MAX_TIMES = 3; // max number of wood
static final int RECORD_MAX_TIMES_OTHER_HIT_TREE = 10; // if hit 10 times other trees, reset wood
private static final HashMap<Integer, Integer> ColliderTypeToWoodItemID = new HashMap<>();
static {
/* define wood types which reflected to item id*/
ColliderTypeToWoodItemID.put(1, 101301);
ColliderTypeToWoodItemID.put(2, 101302);
ColliderTypeToWoodItemID.put(3, 101303);
ColliderTypeToWoodItemID.put(4, 101304);
ColliderTypeToWoodItemID.put(5, 101305);
ColliderTypeToWoodItemID.put(6, 101306);
ColliderTypeToWoodItemID.put(7, 101307);
ColliderTypeToWoodItemID.put(8, 101308);
ColliderTypeToWoodItemID.put(9, 101309);
ColliderTypeToWoodItemID.put(10, 101310);
ColliderTypeToWoodItemID.put(11, 101311);
ColliderTypeToWoodItemID.put(12, 101312);
ColliderTypeToWoodItemID.put(13, 101313);
ColliderTypeToWoodItemID.put(14, 101314);
ColliderTypeToWoodItemID.put(15, 101315);
ColliderTypeToWoodItemID.put(16, 101316);
ColliderTypeToWoodItemID.put(17, 101317);
}
private final ArrayList<HitTreeRecord> currentRecord;
public DeforestationManager(Player player) {
super(player);
this.currentRecord = new ArrayList<>();
}
public void resetWood() {
synchronized (currentRecord) {
currentRecord.clear();
}
}
public void onDeforestationInvoke(HitTreeNotifyOuterClass.HitTreeNotify hit) {
synchronized (currentRecord) {
// Grasscutter.getLogger().info("onDeforestationInvoke! Wood records {}", currentRecord);
VectorOuterClass.Vector hitPosition = hit.getTreePos();
int woodType = hit.getTreeType();
if (ColliderTypeToWoodItemID.containsKey(woodType)) { // is a available wood type
Scene scene = player.getScene();
int itemId = ColliderTypeToWoodItemID.get(woodType);
int positionHash = hitPosition.hashCode();
HitTreeRecord record = searchRecord(positionHash);
if (record == null) {
record = new HitTreeRecord(positionHash);
} else {
currentRecord.remove(record); // move it to last position
}
currentRecord.add(record);
if (currentRecord.size() > RECORD_MAX_TIMES_OTHER_HIT_TREE) {
currentRecord.remove(0);
}
if (record.record()) {
EntityItem entity =
new EntityItem(
scene,
null,
GameData.getItemDataMap().get(itemId),
new Position(hitPosition.getX(), hitPosition.getY(), hitPosition.getZ()),
1,
false);
scene.addEntity(entity);
}
// record.record()=false : too many wood they have deforested, no more wood dropped!
} else {
Grasscutter.getLogger().warn("No wood type {} found.", woodType);
}
}
// unknown wood type
}
private HitTreeRecord searchRecord(int id) {
for (HitTreeRecord record : currentRecord) {
if (record.getUnique() == id) {
return record;
}
}
return null;
}
}
package emu.grasscutter.game.managers.deforestation;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.HitTreeNotifyOuterClass;
import emu.grasscutter.net.proto.VectorOuterClass;
import emu.grasscutter.utils.Position;
import java.util.ArrayList;
import java.util.HashMap;
public class DeforestationManager extends BasePlayerManager {
static final int RECORD_EXPIRED_SECONDS = 60 * 5; // 5 min
static final int RECORD_MAX_TIMES = 3; // max number of wood
static final int RECORD_MAX_TIMES_OTHER_HIT_TREE = 10; // if hit 10 times other trees, reset wood
private static final HashMap<Integer, Integer> ColliderTypeToWoodItemID = new HashMap<>();
static {
/* define wood types which reflected to item id*/
ColliderTypeToWoodItemID.put(1, 101301);
ColliderTypeToWoodItemID.put(2, 101302);
ColliderTypeToWoodItemID.put(3, 101303);
ColliderTypeToWoodItemID.put(4, 101304);
ColliderTypeToWoodItemID.put(5, 101305);
ColliderTypeToWoodItemID.put(6, 101306);
ColliderTypeToWoodItemID.put(7, 101307);
ColliderTypeToWoodItemID.put(8, 101308);
ColliderTypeToWoodItemID.put(9, 101309);
ColliderTypeToWoodItemID.put(10, 101310);
ColliderTypeToWoodItemID.put(11, 101311);
ColliderTypeToWoodItemID.put(12, 101312);
ColliderTypeToWoodItemID.put(13, 101313);
ColliderTypeToWoodItemID.put(14, 101314);
ColliderTypeToWoodItemID.put(15, 101315);
ColliderTypeToWoodItemID.put(16, 101316);
ColliderTypeToWoodItemID.put(17, 101317);
}
private final ArrayList<HitTreeRecord> currentRecord;
public DeforestationManager(Player player) {
super(player);
this.currentRecord = new ArrayList<>();
}
public void resetWood() {
synchronized (currentRecord) {
currentRecord.clear();
}
}
public void onDeforestationInvoke(HitTreeNotifyOuterClass.HitTreeNotify hit) {
synchronized (currentRecord) {
// Grasscutter.getLogger().info("onDeforestationInvoke! Wood records {}", currentRecord);
VectorOuterClass.Vector hitPosition = hit.getTreePos();
int woodType = hit.getTreeType();
if (ColliderTypeToWoodItemID.containsKey(woodType)) { // is a available wood type
Scene scene = player.getScene();
int itemId = ColliderTypeToWoodItemID.get(woodType);
int positionHash = hitPosition.hashCode();
HitTreeRecord record = searchRecord(positionHash);
if (record == null) {
record = new HitTreeRecord(positionHash);
} else {
currentRecord.remove(record); // move it to last position
}
currentRecord.add(record);
if (currentRecord.size() > RECORD_MAX_TIMES_OTHER_HIT_TREE) {
currentRecord.remove(0);
}
if (record.record()) {
EntityItem entity =
new EntityItem(
scene,
null,
GameData.getItemDataMap().get(itemId),
new Position(hitPosition.getX(), hitPosition.getY(), hitPosition.getZ()),
1,
false);
scene.addEntity(entity);
}
// record.record()=false : too many wood they have deforested, no more wood dropped!
} else {
Grasscutter.getLogger().warn("No wood type {} found.", woodType);
}
}
// unknown wood type
}
private HitTreeRecord searchRecord(int id) {
for (HitTreeRecord record : currentRecord) {
if (record.getUnique() == id) {
return record;
}
}
return null;
}
}

View File

@@ -1,48 +1,48 @@
package emu.grasscutter.game.managers.deforestation;
public class HitTreeRecord {
private final int unique;
private short count; // hit this tree times
private long time; // last available hitting time
HitTreeRecord(int unique) {
this.count = 0;
this.time = 0;
this.unique = unique;
}
/** reset hit time */
private void resetTime() {
this.time = System.currentTimeMillis();
}
/** commit hit behavior */
public boolean record() {
if (this.count < DeforestationManager.RECORD_MAX_TIMES) {
this.count++;
resetTime();
return true;
}
// check expired
boolean isWaiting =
System.currentTimeMillis() - this.time
< DeforestationManager.RECORD_EXPIRED_SECONDS * 1000L;
if (isWaiting) {
return false;
} else {
this.count = 1;
resetTime();
return true;
}
}
/** get unique id */
public int getUnique() {
return unique;
}
@Override
public String toString() {
return "HitTreeRecord{" + "unique=" + unique + ", count=" + count + ", time=" + time + '}';
}
}
package emu.grasscutter.game.managers.deforestation;
public class HitTreeRecord {
private final int unique;
private short count; // hit this tree times
private long time; // last available hitting time
HitTreeRecord(int unique) {
this.count = 0;
this.time = 0;
this.unique = unique;
}
/** reset hit time */
private void resetTime() {
this.time = System.currentTimeMillis();
}
/** commit hit behavior */
public boolean record() {
if (this.count < DeforestationManager.RECORD_MAX_TIMES) {
this.count++;
resetTime();
return true;
}
// check expired
boolean isWaiting =
System.currentTimeMillis() - this.time
< DeforestationManager.RECORD_EXPIRED_SECONDS * 1000L;
if (isWaiting) {
return false;
} else {
this.count = 1;
resetTime();
return true;
}
}
/** get unique id */
public int getUnique() {
return unique;
}
@Override
public String toString() {
return "HitTreeRecord{" + "unique=" + unique + ", count=" + count + ", time=" + time + '}';
}
}

View File

@@ -1,16 +1,16 @@
package emu.grasscutter.game.managers.energy;
import java.util.List;
public class SkillParticleGenerationEntry {
private int avatarId;
private List<SkillParticleGenerationInfo> amountList;
public int getAvatarId() {
return this.avatarId;
}
public List<SkillParticleGenerationInfo> getAmountList() {
return this.amountList;
}
}
package emu.grasscutter.game.managers.energy;
import java.util.List;
public class SkillParticleGenerationEntry {
private int avatarId;
private List<SkillParticleGenerationInfo> amountList;
public int getAvatarId() {
return this.avatarId;
}
public List<SkillParticleGenerationInfo> getAmountList() {
return this.amountList;
}
}

View File

@@ -1,14 +1,14 @@
package emu.grasscutter.game.managers.energy;
public class SkillParticleGenerationInfo {
private int value;
private int chance;
public int getValue() {
return this.value;
}
public int getChance() {
return this.chance;
}
}
package emu.grasscutter.game.managers.energy;
public class SkillParticleGenerationInfo {
private int value;
private int chance;
public int getValue() {
return this.value;
}
public int getChance() {
return this.chance;
}
}

View File

@@ -1,96 +1,96 @@
package emu.grasscutter.game.managers.forging;
import dev.morphia.annotations.Entity;
@Entity
public class ActiveForgeData {
private int forgeId;
private int avatarId;
private int count;
private int startTime;
private int forgeTime;
private int lastUnfinishedCount;
private boolean changed;
public int getFinishedCount(int currentTime) {
int timeDelta = currentTime - this.startTime;
int finishedCount = timeDelta / this.forgeTime;
return Math.min(finishedCount, this.count);
}
public int getUnfinishedCount(int currentTime) {
return this.count - this.getFinishedCount(currentTime);
}
public int getNextFinishTimestamp(int currentTime) {
return (currentTime >= this.getTotalFinishTimestamp())
? this.getTotalFinishTimestamp()
: (this.getFinishedCount(currentTime) * this.forgeTime + this.forgeTime + this.startTime);
}
public int getTotalFinishTimestamp() {
return this.startTime + this.forgeTime * this.count;
}
public int getForgeId() {
return this.forgeId;
}
public void setForgeId(int value) {
this.forgeId = value;
}
public int getAvatarId() {
return this.avatarId;
}
public void setAvatarId(int value) {
this.avatarId = value;
}
public int getCount() {
return count;
}
public void setCount(int value) {
this.count = value;
}
public int getStartTime() {
return this.startTime;
}
public void setStartTime(int value) {
this.startTime = value;
}
public int getForgeTime() {
return this.forgeTime;
}
public void setForgeTime(int value) {
this.forgeTime = value;
}
public boolean isChanged() {
return this.changed;
}
public void setChanged(boolean value) {
this.changed = value;
}
public boolean updateChanged(int currentTime) {
int currentUnfinished = this.getUnfinishedCount(currentTime);
if (currentUnfinished != this.lastUnfinishedCount) {
this.changed = true;
this.lastUnfinishedCount = currentUnfinished;
}
return this.changed;
}
}
package emu.grasscutter.game.managers.forging;
import dev.morphia.annotations.Entity;
@Entity
public class ActiveForgeData {
private int forgeId;
private int avatarId;
private int count;
private int startTime;
private int forgeTime;
private int lastUnfinishedCount;
private boolean changed;
public int getFinishedCount(int currentTime) {
int timeDelta = currentTime - this.startTime;
int finishedCount = timeDelta / this.forgeTime;
return Math.min(finishedCount, this.count);
}
public int getUnfinishedCount(int currentTime) {
return this.count - this.getFinishedCount(currentTime);
}
public int getNextFinishTimestamp(int currentTime) {
return (currentTime >= this.getTotalFinishTimestamp())
? this.getTotalFinishTimestamp()
: (this.getFinishedCount(currentTime) * this.forgeTime + this.forgeTime + this.startTime);
}
public int getTotalFinishTimestamp() {
return this.startTime + this.forgeTime * this.count;
}
public int getForgeId() {
return this.forgeId;
}
public void setForgeId(int value) {
this.forgeId = value;
}
public int getAvatarId() {
return this.avatarId;
}
public void setAvatarId(int value) {
this.avatarId = value;
}
public int getCount() {
return count;
}
public void setCount(int value) {
this.count = value;
}
public int getStartTime() {
return this.startTime;
}
public void setStartTime(int value) {
this.startTime = value;
}
public int getForgeTime() {
return this.forgeTime;
}
public void setForgeTime(int value) {
this.forgeTime = value;
}
public boolean isChanged() {
return this.changed;
}
public void setChanged(boolean value) {
this.changed = value;
}
public boolean updateChanged(int currentTime) {
int currentUnfinished = this.getUnfinishedCount(currentTime);
if (currentUnfinished != this.lastUnfinishedCount) {
this.changed = true;
this.lastUnfinishedCount = currentUnfinished;
}
return this.changed;
}
}

View File

@@ -1,318 +1,318 @@
package emu.grasscutter.game.managers.forging;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ForgeData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.ForgeQueueDataOuterClass.ForgeQueueData;
import emu.grasscutter.net.proto.ForgeQueueManipulateReqOuterClass.ForgeQueueManipulateReq;
import emu.grasscutter.net.proto.ForgeQueueManipulateTypeOuterClass.ForgeQueueManipulateType;
import emu.grasscutter.net.proto.ForgeStartReqOuterClass.ForgeStartReq;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.Utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ForgingManager extends BasePlayerManager {
public ForgingManager(Player player) {
super(player);
}
/**********
* Blueprint unlocking.
**********/
public boolean unlockForgingBlueprint(int id) {
// Tell the client that this blueprint is now unlocked and add the unlocked item to the player.
if (!this.player.getUnlockedForgingBlueprints().add(id)) {
return false; // Already unlocked
}
this.player.sendPacket(new PacketForgeFormulaDataNotify(id));
return true;
}
/**********
* Communicate forging information to the client.
**********/
private synchronized int determineNumberOfQueues() {
int adventureRank = player.getLevel();
return (adventureRank >= 15) ? 4 : (adventureRank >= 10) ? 3 : (adventureRank >= 5) ? 2 : 1;
}
private synchronized Map<Integer, ForgeQueueData> determineCurrentForgeQueueData() {
Map<Integer, ForgeQueueData> res = new HashMap<>();
int currentTime = Utils.getCurrentSeconds();
// Create queue information for all active forges.
for (int i = 0; i < this.player.getActiveForges().size(); i++) {
ActiveForgeData activeForge = this.player.getActiveForges().get(i);
ForgeQueueData data =
ForgeQueueData.newBuilder()
.setQueueId(i + 1)
.setForgeId(activeForge.getForgeId())
.setFinishCount(activeForge.getFinishedCount(currentTime))
.setUnfinishCount(activeForge.getUnfinishedCount(currentTime))
.setTotalFinishTimestamp(activeForge.getTotalFinishTimestamp())
.setNextFinishTimestamp(activeForge.getNextFinishTimestamp(currentTime))
.setAvatarId(activeForge.getAvatarId())
.build();
res.put(i + 1, data);
}
return res;
}
public synchronized void sendForgeDataNotify() {
// Determine the number of queues and unlocked items.
int numQueues = this.determineNumberOfQueues();
var unlockedItems = this.player.getUnlockedForgingBlueprints();
var queueData = this.determineCurrentForgeQueueData();
// Send notification.
this.player.sendPacket(new PacketForgeDataNotify(unlockedItems, numQueues, queueData));
}
public synchronized void handleForgeGetQueueDataReq() {
// Determine the number of queues.
int numQueues = this.determineNumberOfQueues();
var queueData = this.determineCurrentForgeQueueData();
// Reply.
this.player.sendPacket(new PacketForgeGetQueueDataRsp(Retcode.RET_SUCC, numQueues, queueData));
}
/**********
* Initiate forging process.
**********/
private synchronized void sendForgeQueueDataNotify() {
var queueData = this.determineCurrentForgeQueueData();
this.player.sendPacket(new PacketForgeQueueDataNotify(queueData, List.of()));
}
private synchronized void sendForgeQueueDataNotify(boolean hasRemoved) {
var queueData = this.determineCurrentForgeQueueData();
if (hasRemoved) {
this.player.sendPacket(new PacketForgeQueueDataNotify(Map.of(), List.of(1, 2, 3, 4)));
}
this.player.sendPacket(new PacketForgeQueueDataNotify(queueData, List.of()));
}
public synchronized void handleForgeStartReq(ForgeStartReq req) {
// Refuse if all queues are already full.
if (this.player.getActiveForges().size() >= this.determineNumberOfQueues()) {
this.player.sendPacket(new PacketForgeStartRsp(Retcode.RET_FORGE_QUEUE_FULL));
return;
}
// Get the required forging information for the target item.
if (!GameData.getForgeDataMap().containsKey(req.getForgeId())) {
this.player.sendPacket(
new PacketForgeStartRsp(Retcode.RET_FAIL)); // ToDo: Probably the wrong return code.
return;
}
ForgeData forgeData = GameData.getForgeDataMap().get(req.getForgeId());
// Check if the player has sufficient forge points.
int requiredPoints = forgeData.getForgePoint() * req.getForgeCount();
if (requiredPoints > this.player.getForgePoints()) {
this.player.sendPacket(new PacketForgeStartRsp(Retcode.RET_FORGE_POINT_NOT_ENOUGH));
return;
}
// Check if we have enough of each material and consume.
List<ItemParamData> material = new ArrayList<>(forgeData.getMaterialItems());
material.add(new ItemParamData(202, forgeData.getScoinCost()));
boolean success =
player.getInventory().payItems(material, req.getForgeCount(), ActionReason.ForgeCost);
if (!success) {
// TODO:I'm not sure this one is correct.
this.player.sendPacket(
new PacketForgeStartRsp(
Retcode.RET_ITEM_COUNT_NOT_ENOUGH)); // ToDo: Probably the wrong return code.
}
// Consume forge points.
this.player.setForgePoints(this.player.getForgePoints() - requiredPoints);
// Create and add active forge.
ActiveForgeData activeForge = new ActiveForgeData();
activeForge.setForgeId(req.getForgeId());
activeForge.setAvatarId(req.getAvatarId());
activeForge.setCount(req.getForgeCount());
activeForge.setStartTime(Utils.getCurrentSeconds());
activeForge.setForgeTime(forgeData.getForgeTime());
this.player.getActiveForges().add(activeForge);
// Done.
this.sendForgeQueueDataNotify();
this.player.sendPacket(new PacketForgeStartRsp(Retcode.RET_SUCC));
}
/**********
* Forge queue manipulation (obtaining results and cancelling forges).
**********/
private synchronized void obtainItems(int queueId) {
// Determine how many items are finished.
int currentTime = Utils.getCurrentSeconds();
ActiveForgeData forge = this.player.getActiveForges().get(queueId - 1);
int finished = forge.getFinishedCount(currentTime);
int unfinished = forge.getUnfinishedCount(currentTime);
// Sanity check: Are any items finished?
if (finished <= 0) {
return;
}
// Give finished items to the player.
ForgeData data = GameData.getForgeDataMap().get(forge.getForgeId());
int resultId = data.getResultItemId() > 0 ? data.getResultItemId() : data.getShowItemId();
ItemData resultItemData = GameData.getItemDataMap().get(resultId);
GameItem addItem = new GameItem(resultItemData, data.getResultItemCount() * finished);
this.player.getInventory().addItem(addItem);
// Battle pass trigger handler
this.player
.getBattlePassManager()
.triggerMission(WatcherTriggerType.TRIGGER_DO_FORGE, 0, finished);
// Replace active forge with a new one for the unfinished items, if there are any.
if (unfinished > 0) {
ActiveForgeData remainingForge = new ActiveForgeData();
remainingForge.setForgeId(forge.getForgeId());
remainingForge.setAvatarId(forge.getAvatarId());
remainingForge.setCount(unfinished);
remainingForge.setForgeTime(forge.getForgeTime());
remainingForge.setStartTime(forge.getStartTime() + finished * forge.getForgeTime());
this.player.getActiveForges().set(queueId - 1, remainingForge);
this.sendForgeQueueDataNotify();
}
// Otherwise, completely remove it.
else {
this.player.getActiveForges().remove(queueId - 1);
// this.sendForgeQueueDataNotify(queueId);
this.sendForgeQueueDataNotify(true);
}
// Send response.
this.player.sendPacket(
new PacketForgeQueueManipulateRsp(
Retcode.RET_SUCC,
ForgeQueueManipulateType.FORGE_QUEUE_MANIPULATE_TYPE_RECEIVE_OUTPUT,
List.of(addItem),
List.of(),
List.of()));
}
private synchronized void cancelForge(int queueId) {
// Make sure there are no unfinished items.
int currentTime = Utils.getCurrentSeconds();
ActiveForgeData forge = this.player.getActiveForges().get(queueId - 1);
if (forge.getFinishedCount(currentTime) > 0) {
return;
}
// Return material items to the player.
ForgeData data = GameData.getForgeDataMap().get(forge.getForgeId());
var returnItems = new ArrayList<GameItem>();
for (var material : data.getMaterialItems()) {
if (material.getItemId() == 0) {
continue;
}
ItemData resultItemData = GameData.getItemDataMap().get(material.getItemId());
GameItem returnItem =
new GameItem(resultItemData, material.getItemCount() * forge.getCount());
this.player.getInventory().addItem(returnItem);
returnItems.add(returnItem);
}
// Return Mora to the player.
this.player.setMora(this.player.getMora() + data.getScoinCost() * forge.getCount());
ItemData moraItem = GameData.getItemDataMap().get(202);
GameItem returnMora = new GameItem(moraItem, data.getScoinCost() * forge.getCount());
returnItems.add(returnMora);
// Return forge points to the player.
int requiredPoints = data.getForgePoint() * forge.getCount();
int newPoints = Math.min(this.player.getForgePoints() + requiredPoints, 300_000);
this.player.setForgePoints(newPoints);
// Remove the forge queue.
this.player.getActiveForges().remove(queueId - 1);
this.sendForgeQueueDataNotify(true);
// Send response.
this.player.sendPacket(
new PacketForgeQueueManipulateRsp(
Retcode.RET_SUCC,
ForgeQueueManipulateType.FORGE_QUEUE_MANIPULATE_TYPE_STOP_FORGE,
List.of(),
returnItems,
List.of()));
}
public synchronized void handleForgeQueueManipulateReq(ForgeQueueManipulateReq req) {
// Get info from the request.
int queueId = req.getForgeQueueId();
var manipulateType = req.getManipulateType();
// Handle according to the manipulation type.
switch (manipulateType) {
case FORGE_QUEUE_MANIPULATE_TYPE_RECEIVE_OUTPUT -> this.obtainItems(queueId);
case FORGE_QUEUE_MANIPULATE_TYPE_STOP_FORGE -> this.cancelForge(queueId);
default -> {} // Should never happen.
}
}
/**********
* Periodic forging updates.
**********/
public synchronized void sendPlayerForgingUpdate() {
int currentTime = Utils.getCurrentSeconds();
// Determine if sending an update is necessary.
// We only send an update if there are forges in the forge queue
// that have changed since the last notification.
if (this.player.getActiveForges().size() <= 0) {
return;
}
boolean hasChanges =
this.player.getActiveForges().stream().anyMatch(forge -> forge.updateChanged(currentTime));
if (!hasChanges) {
return;
}
// Send notification.
this.sendForgeQueueDataNotify();
// Reset changed flags.
this.player.getActiveForges().stream().forEach(forge -> forge.setChanged(false));
}
}
package emu.grasscutter.game.managers.forging;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ForgeData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.ForgeQueueDataOuterClass.ForgeQueueData;
import emu.grasscutter.net.proto.ForgeQueueManipulateReqOuterClass.ForgeQueueManipulateReq;
import emu.grasscutter.net.proto.ForgeQueueManipulateTypeOuterClass.ForgeQueueManipulateType;
import emu.grasscutter.net.proto.ForgeStartReqOuterClass.ForgeStartReq;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.Utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ForgingManager extends BasePlayerManager {
public ForgingManager(Player player) {
super(player);
}
/**********
* Blueprint unlocking.
**********/
public boolean unlockForgingBlueprint(int id) {
// Tell the client that this blueprint is now unlocked and add the unlocked item to the player.
if (!this.player.getUnlockedForgingBlueprints().add(id)) {
return false; // Already unlocked
}
this.player.sendPacket(new PacketForgeFormulaDataNotify(id));
return true;
}
/**********
* Communicate forging information to the client.
**********/
private synchronized int determineNumberOfQueues() {
int adventureRank = player.getLevel();
return (adventureRank >= 15) ? 4 : (adventureRank >= 10) ? 3 : (adventureRank >= 5) ? 2 : 1;
}
private synchronized Map<Integer, ForgeQueueData> determineCurrentForgeQueueData() {
Map<Integer, ForgeQueueData> res = new HashMap<>();
int currentTime = Utils.getCurrentSeconds();
// Create queue information for all active forges.
for (int i = 0; i < this.player.getActiveForges().size(); i++) {
ActiveForgeData activeForge = this.player.getActiveForges().get(i);
ForgeQueueData data =
ForgeQueueData.newBuilder()
.setQueueId(i + 1)
.setForgeId(activeForge.getForgeId())
.setFinishCount(activeForge.getFinishedCount(currentTime))
.setUnfinishCount(activeForge.getUnfinishedCount(currentTime))
.setTotalFinishTimestamp(activeForge.getTotalFinishTimestamp())
.setNextFinishTimestamp(activeForge.getNextFinishTimestamp(currentTime))
.setAvatarId(activeForge.getAvatarId())
.build();
res.put(i + 1, data);
}
return res;
}
public synchronized void sendForgeDataNotify() {
// Determine the number of queues and unlocked items.
int numQueues = this.determineNumberOfQueues();
var unlockedItems = this.player.getUnlockedForgingBlueprints();
var queueData = this.determineCurrentForgeQueueData();
// Send notification.
this.player.sendPacket(new PacketForgeDataNotify(unlockedItems, numQueues, queueData));
}
public synchronized void handleForgeGetQueueDataReq() {
// Determine the number of queues.
int numQueues = this.determineNumberOfQueues();
var queueData = this.determineCurrentForgeQueueData();
// Reply.
this.player.sendPacket(new PacketForgeGetQueueDataRsp(Retcode.RET_SUCC, numQueues, queueData));
}
/**********
* Initiate forging process.
**********/
private synchronized void sendForgeQueueDataNotify() {
var queueData = this.determineCurrentForgeQueueData();
this.player.sendPacket(new PacketForgeQueueDataNotify(queueData, List.of()));
}
private synchronized void sendForgeQueueDataNotify(boolean hasRemoved) {
var queueData = this.determineCurrentForgeQueueData();
if (hasRemoved) {
this.player.sendPacket(new PacketForgeQueueDataNotify(Map.of(), List.of(1, 2, 3, 4)));
}
this.player.sendPacket(new PacketForgeQueueDataNotify(queueData, List.of()));
}
public synchronized void handleForgeStartReq(ForgeStartReq req) {
// Refuse if all queues are already full.
if (this.player.getActiveForges().size() >= this.determineNumberOfQueues()) {
this.player.sendPacket(new PacketForgeStartRsp(Retcode.RET_FORGE_QUEUE_FULL));
return;
}
// Get the required forging information for the target item.
if (!GameData.getForgeDataMap().containsKey(req.getForgeId())) {
this.player.sendPacket(
new PacketForgeStartRsp(Retcode.RET_FAIL)); // ToDo: Probably the wrong return code.
return;
}
ForgeData forgeData = GameData.getForgeDataMap().get(req.getForgeId());
// Check if the player has sufficient forge points.
int requiredPoints = forgeData.getForgePoint() * req.getForgeCount();
if (requiredPoints > this.player.getForgePoints()) {
this.player.sendPacket(new PacketForgeStartRsp(Retcode.RET_FORGE_POINT_NOT_ENOUGH));
return;
}
// Check if we have enough of each material and consume.
List<ItemParamData> material = new ArrayList<>(forgeData.getMaterialItems());
material.add(new ItemParamData(202, forgeData.getScoinCost()));
boolean success =
player.getInventory().payItems(material, req.getForgeCount(), ActionReason.ForgeCost);
if (!success) {
// TODO:I'm not sure this one is correct.
this.player.sendPacket(
new PacketForgeStartRsp(
Retcode.RET_ITEM_COUNT_NOT_ENOUGH)); // ToDo: Probably the wrong return code.
}
// Consume forge points.
this.player.setForgePoints(this.player.getForgePoints() - requiredPoints);
// Create and add active forge.
ActiveForgeData activeForge = new ActiveForgeData();
activeForge.setForgeId(req.getForgeId());
activeForge.setAvatarId(req.getAvatarId());
activeForge.setCount(req.getForgeCount());
activeForge.setStartTime(Utils.getCurrentSeconds());
activeForge.setForgeTime(forgeData.getForgeTime());
this.player.getActiveForges().add(activeForge);
// Done.
this.sendForgeQueueDataNotify();
this.player.sendPacket(new PacketForgeStartRsp(Retcode.RET_SUCC));
}
/**********
* Forge queue manipulation (obtaining results and cancelling forges).
**********/
private synchronized void obtainItems(int queueId) {
// Determine how many items are finished.
int currentTime = Utils.getCurrentSeconds();
ActiveForgeData forge = this.player.getActiveForges().get(queueId - 1);
int finished = forge.getFinishedCount(currentTime);
int unfinished = forge.getUnfinishedCount(currentTime);
// Sanity check: Are any items finished?
if (finished <= 0) {
return;
}
// Give finished items to the player.
ForgeData data = GameData.getForgeDataMap().get(forge.getForgeId());
int resultId = data.getResultItemId() > 0 ? data.getResultItemId() : data.getShowItemId();
ItemData resultItemData = GameData.getItemDataMap().get(resultId);
GameItem addItem = new GameItem(resultItemData, data.getResultItemCount() * finished);
this.player.getInventory().addItem(addItem);
// Battle pass trigger handler
this.player
.getBattlePassManager()
.triggerMission(WatcherTriggerType.TRIGGER_DO_FORGE, 0, finished);
// Replace active forge with a new one for the unfinished items, if there are any.
if (unfinished > 0) {
ActiveForgeData remainingForge = new ActiveForgeData();
remainingForge.setForgeId(forge.getForgeId());
remainingForge.setAvatarId(forge.getAvatarId());
remainingForge.setCount(unfinished);
remainingForge.setForgeTime(forge.getForgeTime());
remainingForge.setStartTime(forge.getStartTime() + finished * forge.getForgeTime());
this.player.getActiveForges().set(queueId - 1, remainingForge);
this.sendForgeQueueDataNotify();
}
// Otherwise, completely remove it.
else {
this.player.getActiveForges().remove(queueId - 1);
// this.sendForgeQueueDataNotify(queueId);
this.sendForgeQueueDataNotify(true);
}
// Send response.
this.player.sendPacket(
new PacketForgeQueueManipulateRsp(
Retcode.RET_SUCC,
ForgeQueueManipulateType.FORGE_QUEUE_MANIPULATE_TYPE_RECEIVE_OUTPUT,
List.of(addItem),
List.of(),
List.of()));
}
private synchronized void cancelForge(int queueId) {
// Make sure there are no unfinished items.
int currentTime = Utils.getCurrentSeconds();
ActiveForgeData forge = this.player.getActiveForges().get(queueId - 1);
if (forge.getFinishedCount(currentTime) > 0) {
return;
}
// Return material items to the player.
ForgeData data = GameData.getForgeDataMap().get(forge.getForgeId());
var returnItems = new ArrayList<GameItem>();
for (var material : data.getMaterialItems()) {
if (material.getItemId() == 0) {
continue;
}
ItemData resultItemData = GameData.getItemDataMap().get(material.getItemId());
GameItem returnItem =
new GameItem(resultItemData, material.getItemCount() * forge.getCount());
this.player.getInventory().addItem(returnItem);
returnItems.add(returnItem);
}
// Return Mora to the player.
this.player.setMora(this.player.getMora() + data.getScoinCost() * forge.getCount());
ItemData moraItem = GameData.getItemDataMap().get(202);
GameItem returnMora = new GameItem(moraItem, data.getScoinCost() * forge.getCount());
returnItems.add(returnMora);
// Return forge points to the player.
int requiredPoints = data.getForgePoint() * forge.getCount();
int newPoints = Math.min(this.player.getForgePoints() + requiredPoints, 300_000);
this.player.setForgePoints(newPoints);
// Remove the forge queue.
this.player.getActiveForges().remove(queueId - 1);
this.sendForgeQueueDataNotify(true);
// Send response.
this.player.sendPacket(
new PacketForgeQueueManipulateRsp(
Retcode.RET_SUCC,
ForgeQueueManipulateType.FORGE_QUEUE_MANIPULATE_TYPE_STOP_FORGE,
List.of(),
returnItems,
List.of()));
}
public synchronized void handleForgeQueueManipulateReq(ForgeQueueManipulateReq req) {
// Get info from the request.
int queueId = req.getForgeQueueId();
var manipulateType = req.getManipulateType();
// Handle according to the manipulation type.
switch (manipulateType) {
case FORGE_QUEUE_MANIPULATE_TYPE_RECEIVE_OUTPUT -> this.obtainItems(queueId);
case FORGE_QUEUE_MANIPULATE_TYPE_STOP_FORGE -> this.cancelForge(queueId);
default -> {} // Should never happen.
}
}
/**********
* Periodic forging updates.
**********/
public synchronized void sendPlayerForgingUpdate() {
int currentTime = Utils.getCurrentSeconds();
// Determine if sending an update is necessary.
// We only send an update if there are forges in the forge queue
// that have changed since the last notification.
if (this.player.getActiveForges().size() <= 0) {
return;
}
boolean hasChanges =
this.player.getActiveForges().stream().anyMatch(forge -> forge.updateChanged(currentTime));
if (!hasChanges) {
return;
}
// Send notification.
this.sendForgeQueueDataNotify();
// Reset changed flags.
this.player.getActiveForges().stream().forEach(forge -> forge.setChanged(false));
}
}

View File

@@ -1,88 +1,88 @@
package emu.grasscutter.game.managers.mapmark;
import emu.grasscutter.config.Configuration;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.MapMarkPointTypeOuterClass.MapMarkPointType;
import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq;
import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq.Operation;
import emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType;
import emu.grasscutter.server.packet.send.PacketMarkMapRsp;
import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
import emu.grasscutter.utils.Position;
import java.util.Map;
public class MapMarksManager extends BasePlayerManager {
public static final int mapMarkMaxCount = 150;
public MapMarksManager(Player player) {
super(player);
}
public Map<String, MapMark> getMapMarks() {
return this.getPlayer().getMapMarks();
}
public void handleMapMarkReq(MarkMapReq req) {
Operation op = req.getOp();
switch (op) {
case OPERATION_ADD -> {
MapMark createMark = new MapMark(req.getMark());
// keep teleporting functionality on fishhook mark.
if (Configuration.GAME_OPTIONS.fishhookTeleport
&& createMark.getMapMarkPointType() == MapMarkPointType.MAP_MARK_POINT_TYPE_FISH_POOL) {
this.teleport(player, createMark);
return;
}
this.addMapMark(createMark);
}
case OPERATION_MOD -> {
MapMark oldMark = new MapMark(req.getOld());
this.removeMapMark(oldMark.getPosition());
MapMark newMark = new MapMark(req.getMark());
this.addMapMark(newMark);
}
case OPERATION_DEL -> {
MapMark deleteMark = new MapMark(req.getMark());
this.removeMapMark(deleteMark.getPosition());
}
}
if (op != Operation.OPERATION_GET) {
this.save();
}
player.getSession().send(new PacketMarkMapRsp(this.getMapMarks()));
}
public String getMapMarkKey(Position position) {
return "x" + (int) position.getX() + "z" + (int) position.getZ();
}
public void removeMapMark(Position position) {
this.getMapMarks().remove(this.getMapMarkKey(position));
}
public void addMapMark(MapMark mapMark) {
if (this.getMapMarks().size() < mapMarkMaxCount) {
this.getMapMarks().put(this.getMapMarkKey(mapMark.getPosition()), mapMark);
}
}
private void teleport(Player player, MapMark mapMark) {
float y;
try {
y = Float.parseFloat(mapMark.getName());
} catch (Exception e) {
y = 300;
}
Position pos = mapMark.getPosition();
player
.getWorld()
.transferPlayerToScene(
player,
mapMark.getSceneId(),
TeleportType.MAP,
new Position(pos.getX(), y, pos.getZ()));
player.getScene().broadcastPacket(new PacketSceneEntityAppearNotify(player));
}
}
package emu.grasscutter.game.managers.mapmark;
import emu.grasscutter.config.Configuration;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.MapMarkPointTypeOuterClass.MapMarkPointType;
import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq;
import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq.Operation;
import emu.grasscutter.server.event.player.PlayerTeleportEvent.TeleportType;
import emu.grasscutter.server.packet.send.PacketMarkMapRsp;
import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify;
import emu.grasscutter.utils.Position;
import java.util.Map;
public class MapMarksManager extends BasePlayerManager {
public static final int mapMarkMaxCount = 150;
public MapMarksManager(Player player) {
super(player);
}
public Map<String, MapMark> getMapMarks() {
return this.getPlayer().getMapMarks();
}
public void handleMapMarkReq(MarkMapReq req) {
Operation op = req.getOp();
switch (op) {
case OPERATION_ADD -> {
MapMark createMark = new MapMark(req.getMark());
// keep teleporting functionality on fishhook mark.
if (Configuration.GAME_OPTIONS.fishhookTeleport
&& createMark.getMapMarkPointType() == MapMarkPointType.MAP_MARK_POINT_TYPE_FISH_POOL) {
this.teleport(player, createMark);
return;
}
this.addMapMark(createMark);
}
case OPERATION_MOD -> {
MapMark oldMark = new MapMark(req.getOld());
this.removeMapMark(oldMark.getPosition());
MapMark newMark = new MapMark(req.getMark());
this.addMapMark(newMark);
}
case OPERATION_DEL -> {
MapMark deleteMark = new MapMark(req.getMark());
this.removeMapMark(deleteMark.getPosition());
}
}
if (op != Operation.OPERATION_GET) {
this.save();
}
player.getSession().send(new PacketMarkMapRsp(this.getMapMarks()));
}
public String getMapMarkKey(Position position) {
return "x" + (int) position.getX() + "z" + (int) position.getZ();
}
public void removeMapMark(Position position) {
this.getMapMarks().remove(this.getMapMarkKey(position));
}
public void addMapMark(MapMark mapMark) {
if (this.getMapMarks().size() < mapMarkMaxCount) {
this.getMapMarks().put(this.getMapMarkKey(mapMark.getPosition()), mapMark);
}
}
private void teleport(Player player, MapMark mapMark) {
float y;
try {
y = Float.parseFloat(mapMark.getName());
} catch (Exception e) {
y = 300;
}
Position pos = mapMark.getPosition();
player
.getWorld()
.transferPlayerToScene(
player,
mapMark.getSceneId(),
TeleportType.MAP,
new Position(pos.getX(), y, pos.getZ()));
player.getScene().broadcastPacket(new PacketSceneEntityAppearNotify(player));
}
}

View File

@@ -1,12 +1,12 @@
package emu.grasscutter.game.managers.stamina;
public interface AfterUpdateStaminaListener {
/**
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's
* current stamina. This gives listeners a chance to intercept this update.
*
* @param reason Why updating stamina.
* @param newStamina New Stamina value.
*/
void onAfterUpdateStamina(String reason, int newStamina, boolean isCharacterStamina);
}
package emu.grasscutter.game.managers.stamina;
public interface AfterUpdateStaminaListener {
/**
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's
* current stamina. This gives listeners a chance to intercept this update.
*
* @param reason Why updating stamina.
* @param newStamina New Stamina value.
*/
void onAfterUpdateStamina(String reason, int newStamina, boolean isCharacterStamina);
}

View File

@@ -1,24 +1,24 @@
package emu.grasscutter.game.managers.stamina;
public interface BeforeUpdateStaminaListener {
/**
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's
* current stamina. This gives listeners a chance to intercept this update.
*
* @param reason Why updating stamina.
* @param newStamina New ABSOLUTE stamina value.
* @return true if you want to cancel this update, otherwise false.
*/
int onBeforeUpdateStamina(String reason, int newStamina, boolean isCharacterStamina);
/**
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's
* current stamina. This gives listeners a chance to intercept this update.
*
* @param reason Why updating stamina.
* @param consumption ConsumptionType and RELATIVE stamina change amount.
* @return true if you want to cancel this update, otherwise false.
*/
Consumption onBeforeUpdateStamina(
String reason, Consumption consumption, boolean isCharacterStamina);
}
package emu.grasscutter.game.managers.stamina;
public interface BeforeUpdateStaminaListener {
/**
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's
* current stamina. This gives listeners a chance to intercept this update.
*
* @param reason Why updating stamina.
* @param newStamina New ABSOLUTE stamina value.
* @return true if you want to cancel this update, otherwise false.
*/
int onBeforeUpdateStamina(String reason, int newStamina, boolean isCharacterStamina);
/**
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's
* current stamina. This gives listeners a chance to intercept this update.
*
* @param reason Why updating stamina.
* @param consumption ConsumptionType and RELATIVE stamina change amount.
* @return true if you want to cancel this update, otherwise false.
*/
Consumption onBeforeUpdateStamina(
String reason, Consumption consumption, boolean isCharacterStamina);
}

View File

@@ -1,17 +1,17 @@
package emu.grasscutter.game.managers.stamina;
public class Consumption {
public ConsumptionType type = ConsumptionType.None;
public int amount = 0;
public Consumption(ConsumptionType type, int amount) {
this.type = type;
this.amount = amount;
}
public Consumption(ConsumptionType type) {
this(type, type.amount);
}
public Consumption() {}
}
package emu.grasscutter.game.managers.stamina;
public class Consumption {
public ConsumptionType type = ConsumptionType.None;
public int amount = 0;
public Consumption(ConsumptionType type, int amount) {
this.type = type;
this.amount = amount;
}
public Consumption(ConsumptionType type) {
this(type, type.amount);
}
public Consumption() {}
}

View File

@@ -1,37 +1,37 @@
package emu.grasscutter.game.managers.stamina;
public enum ConsumptionType {
None(0),
// consume
CLIMBING(-150),
CLIMB_START(-500),
CLIMB_JUMP(-2500),
DASH(-360),
FIGHT(0), // See StaminaManager.getFightConsumption()
FLY(-60),
// Slow swimming is handled per movement, not per second.
// Arm movement frequency depends on gender/age/height.
// TODO: Instead of cost -80 per tick, find a proper way to calculate cost.
SKIFF_DASH(-204),
SPRINT(-1800),
SWIM_DASH_START(-2000),
SWIM_DASH(-204), // -10.2 per second, 5Hz = -204 each tick
SWIMMING(-80),
TALENT_DASH(-300), // -1500 per second, 5Hz = -300 each tick
TALENT_DASH_START(-1000),
// restore
POWERED_FLY(500),
POWERED_SKIFF(500),
RUN(500),
SKIFF(500),
STANDBY(500),
WALK(500);
public final int amount;
ConsumptionType(int amount) {
this.amount = amount;
}
}
package emu.grasscutter.game.managers.stamina;
public enum ConsumptionType {
None(0),
// consume
CLIMBING(-150),
CLIMB_START(-500),
CLIMB_JUMP(-2500),
DASH(-360),
FIGHT(0), // See StaminaManager.getFightConsumption()
FLY(-60),
// Slow swimming is handled per movement, not per second.
// Arm movement frequency depends on gender/age/height.
// TODO: Instead of cost -80 per tick, find a proper way to calculate cost.
SKIFF_DASH(-204),
SPRINT(-1800),
SWIM_DASH_START(-2000),
SWIM_DASH(-204), // -10.2 per second, 5Hz = -204 each tick
SWIMMING(-80),
TALENT_DASH(-300), // -1500 per second, 5Hz = -300 each tick
TALENT_DASH_START(-1000),
// restore
POWERED_FLY(500),
POWERED_SKIFF(500),
RUN(500),
SKIFF(500),
STANDBY(500),
WALK(500);
public final int amount;
ConsumptionType(int amount) {
this.amount = amount;
}
}

View File

@@ -1,23 +1,23 @@
package emu.grasscutter.game.player;
import lombok.NonNull;
public abstract class BasePlayerDataManager {
protected transient Player player;
public BasePlayerDataManager() {}
public BasePlayerDataManager(@NonNull Player player) {
this.player = player;
}
public Player getPlayer() {
return this.player;
}
public void setPlayer(Player player) {
if (this.player == null) {
this.player = player;
}
}
}
package emu.grasscutter.game.player;
import lombok.NonNull;
public abstract class BasePlayerDataManager {
protected transient Player player;
public BasePlayerDataManager() {}
public BasePlayerDataManager(@NonNull Player player) {
this.player = player;
}
public Player getPlayer() {
return this.player;
}
public void setPlayer(Player player) {
if (this.player == null) {
this.player = player;
}
}
}

View File

@@ -1,20 +1,20 @@
package emu.grasscutter.game.player;
import lombok.NonNull;
public abstract class BasePlayerManager {
protected final transient Player player;
public BasePlayerManager(@NonNull Player player) {
this.player = player;
}
public Player getPlayer() {
return this.player;
}
/** Saves the player to the database */
public void save() {
getPlayer().save();
}
}
package emu.grasscutter.game.player;
import lombok.NonNull;
public abstract class BasePlayerManager {
protected final transient Player player;
public BasePlayerManager(@NonNull Player player) {
this.player = player;
}
public Player getPlayer() {
return this.player;
}
/** Saves the player to the database */
public void save() {
getPlayer().save();
}
}

View File

@@ -1,64 +1,64 @@
package emu.grasscutter.game.player;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.ForwardTypeOuterClass.ForwardType;
import java.util.ArrayList;
import java.util.List;
public class InvokeHandler<T> {
private final List<T> entryListForwardAll;
private final List<T> entryListForwardAllExceptCur;
private final List<T> entryListForwardHost;
private final Class<? extends BasePacket> packetClass;
public InvokeHandler(Class<? extends BasePacket> packetClass) {
this.entryListForwardAll = new ArrayList<>();
this.entryListForwardAllExceptCur = new ArrayList<>();
this.entryListForwardHost = new ArrayList<>();
this.packetClass = packetClass;
}
public synchronized void addEntry(ForwardType forward, T entry) {
switch (forward) {
case FORWARD_TYPE_TO_ALL -> entryListForwardAll.add(entry);
case FORWARD_TYPE_TO_ALL_EXCEPT_CUR,
FORWARD_TYPE_TO_ALL_EXIST_EXCEPT_CUR -> entryListForwardAllExceptCur.add(entry);
case FORWARD_TYPE_TO_HOST -> entryListForwardHost.add(entry);
default -> {}
}
}
public synchronized void update(Player player) {
if (player.getWorld() == null || player.getScene() == null) {
this.entryListForwardAll.clear();
this.entryListForwardAllExceptCur.clear();
this.entryListForwardHost.clear();
return;
}
try {
if (entryListForwardAll.size() > 0) {
BasePacket packet =
packetClass.getDeclaredConstructor(List.class).newInstance(this.entryListForwardAll);
player.getScene().broadcastPacket(packet);
this.entryListForwardAll.clear();
}
if (entryListForwardAllExceptCur.size() > 0) {
BasePacket packet =
packetClass
.getDeclaredConstructor(List.class)
.newInstance(this.entryListForwardAllExceptCur);
player.getScene().broadcastPacketToOthers(player, packet);
this.entryListForwardAllExceptCur.clear();
}
if (entryListForwardHost.size() > 0) {
BasePacket packet =
packetClass.getDeclaredConstructor(List.class).newInstance(this.entryListForwardHost);
player.getWorld().getHost().sendPacket(packet);
this.entryListForwardHost.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package emu.grasscutter.game.player;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.ForwardTypeOuterClass.ForwardType;
import java.util.ArrayList;
import java.util.List;
public class InvokeHandler<T> {
private final List<T> entryListForwardAll;
private final List<T> entryListForwardAllExceptCur;
private final List<T> entryListForwardHost;
private final Class<? extends BasePacket> packetClass;
public InvokeHandler(Class<? extends BasePacket> packetClass) {
this.entryListForwardAll = new ArrayList<>();
this.entryListForwardAllExceptCur = new ArrayList<>();
this.entryListForwardHost = new ArrayList<>();
this.packetClass = packetClass;
}
public synchronized void addEntry(ForwardType forward, T entry) {
switch (forward) {
case FORWARD_TYPE_TO_ALL -> entryListForwardAll.add(entry);
case FORWARD_TYPE_TO_ALL_EXCEPT_CUR,
FORWARD_TYPE_TO_ALL_EXIST_EXCEPT_CUR -> entryListForwardAllExceptCur.add(entry);
case FORWARD_TYPE_TO_HOST -> entryListForwardHost.add(entry);
default -> {}
}
}
public synchronized void update(Player player) {
if (player.getWorld() == null || player.getScene() == null) {
this.entryListForwardAll.clear();
this.entryListForwardAllExceptCur.clear();
this.entryListForwardHost.clear();
return;
}
try {
if (entryListForwardAll.size() > 0) {
BasePacket packet =
packetClass.getDeclaredConstructor(List.class).newInstance(this.entryListForwardAll);
player.getScene().broadcastPacket(packet);
this.entryListForwardAll.clear();
}
if (entryListForwardAllExceptCur.size() > 0) {
BasePacket packet =
packetClass
.getDeclaredConstructor(List.class)
.newInstance(this.entryListForwardAllExceptCur);
player.getScene().broadcastPacketToOthers(player, packet);
this.entryListForwardAllExceptCur.clear();
}
if (entryListForwardHost.size() > 0) {
BasePacket packet =
packetClass.getDeclaredConstructor(List.class).newInstance(this.entryListForwardHost);
player.getWorld().getHost().sendPacket(packet);
this.entryListForwardHost.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -1,64 +1,64 @@
package emu.grasscutter.game.player;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.BirthdayOuterClass.Birthday;
@Entity
public class PlayerBirthday {
private int day;
private int month;
public PlayerBirthday() {
this.day = 0;
this.month = 0;
}
public PlayerBirthday(int day, int month) {
this.day = day;
this.month = month;
}
public PlayerBirthday set(PlayerBirthday birth) {
this.day = birth.day;
this.month = birth.month;
return this;
}
public PlayerBirthday set(int d, int m) {
this.day = d;
this.month = m;
return this;
}
public int getDay() {
return this.day;
}
public PlayerBirthday setDay(int value) {
this.day = value;
return this;
}
public int getMonth() {
return this.month;
}
public PlayerBirthday setMonth(int value) {
this.month = value;
return this;
}
public Birthday toProto() {
return Birthday.newBuilder().setDay(this.getDay()).setMonth(this.getMonth()).build();
}
public Birthday.Builder getFilledProtoWhenNotEmpty() {
if (this.getDay() > 0) {
return Birthday.newBuilder().setDay(this.getDay()).setMonth(this.getMonth());
}
return Birthday.newBuilder();
}
}
package emu.grasscutter.game.player;
import dev.morphia.annotations.Entity;
import emu.grasscutter.net.proto.BirthdayOuterClass.Birthday;
@Entity
public class PlayerBirthday {
private int day;
private int month;
public PlayerBirthday() {
this.day = 0;
this.month = 0;
}
public PlayerBirthday(int day, int month) {
this.day = day;
this.month = month;
}
public PlayerBirthday set(PlayerBirthday birth) {
this.day = birth.day;
this.month = birth.month;
return this;
}
public PlayerBirthday set(int d, int m) {
this.day = d;
this.month = m;
return this;
}
public int getDay() {
return this.day;
}
public PlayerBirthday setDay(int value) {
this.day = value;
return this;
}
public int getMonth() {
return this.month;
}
public PlayerBirthday setMonth(int value) {
this.month = value;
return this;
}
public Birthday toProto() {
return Birthday.newBuilder().setDay(this.getDay()).setMonth(this.getMonth()).build();
}
public Birthday.Builder getFilledProtoWhenNotEmpty() {
if (this.getDay() > 0) {
return Birthday.newBuilder().setDay(this.getDay()).setMonth(this.getMonth());
}
return Birthday.newBuilder();
}
}

View File

@@ -1,68 +1,68 @@
package emu.grasscutter.game.player;
import dev.morphia.annotations.Entity;
import java.util.HashMap;
import java.util.Map;
@Entity(useDiscriminator = false)
public class PlayerCollectionRecords {
private Map<Integer, CollectionRecord> records;
private Map<Integer, CollectionRecord> getRecords() {
if (records == null) {
records = new HashMap<>();
}
return records;
}
public void addRecord(int configId, long expiredMillisecond) {
Map<Integer, CollectionRecord> records;
synchronized (records = getRecords()) {
records.put(
configId,
new CollectionRecord(configId, expiredMillisecond + System.currentTimeMillis()));
}
}
public boolean findRecord(int configId) {
Map<Integer, CollectionRecord> records;
synchronized (records = getRecords()) {
CollectionRecord record = records.get(configId);
if (record == null) {
return false;
}
boolean expired = record.getExpiredTime() < System.currentTimeMillis();
if (expired) {
records.remove(configId);
return false;
}
return true;
}
}
@Entity
public static class CollectionRecord {
private int configId;
private long expiredTime;
@Deprecated // Morphia
public CollectionRecord() {}
public CollectionRecord(int configId, long expiredTime) {
this.configId = configId;
this.expiredTime = expiredTime;
}
public int getConfigId() {
return configId;
}
public long getExpiredTime() {
return expiredTime;
}
}
}
package emu.grasscutter.game.player;
import dev.morphia.annotations.Entity;
import java.util.HashMap;
import java.util.Map;
@Entity(useDiscriminator = false)
public class PlayerCollectionRecords {
private Map<Integer, CollectionRecord> records;
private Map<Integer, CollectionRecord> getRecords() {
if (records == null) {
records = new HashMap<>();
}
return records;
}
public void addRecord(int configId, long expiredMillisecond) {
Map<Integer, CollectionRecord> records;
synchronized (records = getRecords()) {
records.put(
configId,
new CollectionRecord(configId, expiredMillisecond + System.currentTimeMillis()));
}
}
public boolean findRecord(int configId) {
Map<Integer, CollectionRecord> records;
synchronized (records = getRecords()) {
CollectionRecord record = records.get(configId);
if (record == null) {
return false;
}
boolean expired = record.getExpiredTime() < System.currentTimeMillis();
if (expired) {
records.remove(configId);
return false;
}
return true;
}
}
@Entity
public static class CollectionRecord {
private int configId;
private long expiredTime;
@Deprecated // Morphia
public CollectionRecord() {}
public CollectionRecord(int configId, long expiredTime) {
this.configId = configId;
this.expiredTime = expiredTime;
}
public int getConfigId() {
return configId;
}
public long getExpiredTime() {
return expiredTime;
}
}
}

View File

@@ -1,213 +1,213 @@
package emu.grasscutter.game.props;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public enum ActionReason {
None(0),
QuestItem(1),
QuestReward(2),
Trifle(3),
Shop(4),
PlayerUpgradeReward(5),
AddAvatar(6),
GadgetEnvAnimal(7),
MonsterEnvAnimal(8),
Compound(9),
Cook(10),
Gather(11),
MailAttachment(12),
CityLevelupReturn(15),
CityLevelupReward(17),
AreaExploreReward(18),
UnlockPointReward(19),
DungeonFirstPass(20),
DungeonPass(21),
ChangeElemType(23),
FetterOpen(25),
DailyTaskScore(26),
DailyTaskHost(27),
RandTaskHost(28),
Expedition(29),
Gacha(30),
Combine(31),
RandTaskGuest(32),
DailyTaskGuest(33),
ForgeOutput(34),
ForgeReturn(35),
InitAvatar(36),
MonsterDie(37),
Gm(38),
OpenChest(39),
GadgetDie(40),
MonsterChangeHp(41),
SubfieldDrop(42),
PushTipsReward(43),
ActivityMonsterDrop(44),
ActivityGather(45),
ActivitySubfieldDrop(46),
TowerScheduleReward(47),
TowerFloorStarReward(48),
TowerFirstPassReward(49),
TowerDailyReward(50),
HitClientTrivialEntity(51),
OpenWorldBossChest(52),
MaterialDeleteReturn(53),
SignInReward(54),
OpenBlossomChest(55),
Recharge(56),
BonusActivityReward(57),
TowerCommemorativeReward(58),
TowerSkipFloorReward(59),
RechargeBonus(60),
RechargeCard(61),
RechargeCardDaily(62),
RechargeCardReplace(63),
RechargeCardReplaceFree(64),
RechargePlayReplace(65),
MpPlayTakeReward(66),
ActivityWatcher(67),
SalesmanDeliverItem(68),
SalesmanReward(69),
Rebate(70),
McoinExchangeHcoin(71),
DailyTaskExchangeLegendaryKey(72),
UnlockPersonLine(73),
FetterLevelReward(74),
BuyResin(75),
RechargePackage(76),
DeliveryDailyReward(77),
CityReputationLevel(78),
CityReputationQuest(79),
CityReputationRequest(80),
CityReputationExplore(81),
OffergingLevel(82),
RoutineHost(83),
RoutineGuest(84),
TreasureMapSpotToken(89),
TreasureMapBonusLevelReward(90),
TreasureMapMpReward(91),
Convert(92),
OverflowTransform(93),
ActivityAvatarSelectionReward(96),
ActivityWatcherBatch(97),
HitTreeDrop(98),
GetHomeLevelupReward(99),
HomeDefaultFurniture(100),
ActivityCond(101),
BattlePassNotify(102),
PlayerUseItem(1001),
DropItem(1002),
WeaponUpgrade(1011),
WeaponPromote(1012),
WeaponAwaken(1013),
RelicUpgrade(1014),
Ability(1015),
DungeonStatueDrop(1016),
OfflineMsg(1017),
AvatarUpgrade(1018),
AvatarPromote(1019),
QuestAction(1021),
CityLevelup(1022),
UpgradeSkill(1024),
UnlockTalent(1025),
UpgradeProudSkill(1026),
PlayerLevelLimitUp(1027),
DungeonDaily(1028),
ItemGiving(1030),
ForgeCost(1031),
InvestigationReward(1032),
InvestigationTargetReward(1033),
GadgetInteract(1034),
SeaLampCiMaterial(1036),
SeaLampContributionReward(1037),
SeaLampPhaseReward(1038),
SeaLampFlyLamp(1039),
AutoRecover(1040),
ActivityExpireItem(1041),
SubCoinNegative(1042),
BargainDeduct(1043),
BattlePassPaidReward(1044),
BattlePassLevelReward(1045),
TrialAvatarActivityFirstPassReward(1046),
BuyBattlePassLevel(1047),
GrantBirthdayBenefit(1048),
AchievementReward(1049),
AchievementGoalReward(1050),
FirstShareToSocialNetwork(1051),
DestroyMaterial(1052),
CodexLevelupReward(1053),
HuntingOfferReward(1054),
UseWidgetAnchorPoint(1055),
UseWidgetBonfire(1056),
UngradeWeaponReturnMaterial(1057),
UseWidgetOneoffGatherPointDetector(1058),
UseWidgetClientCollector(1059),
UseWidgetClientDetector(1060),
TakeGeneralReward(1061),
AsterTakeSpecialReward(1062),
RemoveCodexBook(1063),
OfferingItem(1064),
UseWidgetGadgetBuilder(1065),
EffigyFirstPassReward(1066),
EffigyReward(1067),
ReunionFirstGiftReward(1068),
ReunionSignInReward(1069),
ReunionWatcherReward(1070),
SalesmanMpReward(1071),
ActionReasionAvatarPromoteReward(1072),
BlessingRedeemReward(1073),
ActionMiracleRingReward(1074),
ExpeditionReward(1075),
TreasureMapRemoveDetector(1076),
MechanicusDungeonTicket(1077),
MechanicusLevelupGear(1078),
MechanicusBattleSettle(1079),
RegionSearchReward(1080),
UnlockCoopChapter(1081),
TakeCoopReward(1082),
FleurFairDungeonReward(1083),
ActivityScore(1084),
ChannellerSlabOneoffDungeonReward(1085),
FurnitureMakeStart(1086),
FurnitureMakeTake(1087),
FurnitureMakeCancel(1088),
FurnitureMakeFastFinish(1089),
ChannellerSlabLoopDungeonFirstPassReward(1090),
ChannellerSlabLoopDungeonScoreReward(1091),
HomeLimitedShopBuy(1092),
HomeCoinCollect(1093);
private static final Int2ObjectMap<ActionReason> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, ActionReason> stringMap = new HashMap<>();
static {
Stream.of(values())
.forEach(
e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private final int value;
ActionReason(int value) {
this.value = value;
}
public static ActionReason getTypeByValue(int value) {
return map.getOrDefault(value, None);
}
public static ActionReason getTypeByName(String name) {
return stringMap.getOrDefault(name, None);
}
public int getValue() {
return value;
}
}
package emu.grasscutter.game.props;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public enum ActionReason {
None(0),
QuestItem(1),
QuestReward(2),
Trifle(3),
Shop(4),
PlayerUpgradeReward(5),
AddAvatar(6),
GadgetEnvAnimal(7),
MonsterEnvAnimal(8),
Compound(9),
Cook(10),
Gather(11),
MailAttachment(12),
CityLevelupReturn(15),
CityLevelupReward(17),
AreaExploreReward(18),
UnlockPointReward(19),
DungeonFirstPass(20),
DungeonPass(21),
ChangeElemType(23),
FetterOpen(25),
DailyTaskScore(26),
DailyTaskHost(27),
RandTaskHost(28),
Expedition(29),
Gacha(30),
Combine(31),
RandTaskGuest(32),
DailyTaskGuest(33),
ForgeOutput(34),
ForgeReturn(35),
InitAvatar(36),
MonsterDie(37),
Gm(38),
OpenChest(39),
GadgetDie(40),
MonsterChangeHp(41),
SubfieldDrop(42),
PushTipsReward(43),
ActivityMonsterDrop(44),
ActivityGather(45),
ActivitySubfieldDrop(46),
TowerScheduleReward(47),
TowerFloorStarReward(48),
TowerFirstPassReward(49),
TowerDailyReward(50),
HitClientTrivialEntity(51),
OpenWorldBossChest(52),
MaterialDeleteReturn(53),
SignInReward(54),
OpenBlossomChest(55),
Recharge(56),
BonusActivityReward(57),
TowerCommemorativeReward(58),
TowerSkipFloorReward(59),
RechargeBonus(60),
RechargeCard(61),
RechargeCardDaily(62),
RechargeCardReplace(63),
RechargeCardReplaceFree(64),
RechargePlayReplace(65),
MpPlayTakeReward(66),
ActivityWatcher(67),
SalesmanDeliverItem(68),
SalesmanReward(69),
Rebate(70),
McoinExchangeHcoin(71),
DailyTaskExchangeLegendaryKey(72),
UnlockPersonLine(73),
FetterLevelReward(74),
BuyResin(75),
RechargePackage(76),
DeliveryDailyReward(77),
CityReputationLevel(78),
CityReputationQuest(79),
CityReputationRequest(80),
CityReputationExplore(81),
OffergingLevel(82),
RoutineHost(83),
RoutineGuest(84),
TreasureMapSpotToken(89),
TreasureMapBonusLevelReward(90),
TreasureMapMpReward(91),
Convert(92),
OverflowTransform(93),
ActivityAvatarSelectionReward(96),
ActivityWatcherBatch(97),
HitTreeDrop(98),
GetHomeLevelupReward(99),
HomeDefaultFurniture(100),
ActivityCond(101),
BattlePassNotify(102),
PlayerUseItem(1001),
DropItem(1002),
WeaponUpgrade(1011),
WeaponPromote(1012),
WeaponAwaken(1013),
RelicUpgrade(1014),
Ability(1015),
DungeonStatueDrop(1016),
OfflineMsg(1017),
AvatarUpgrade(1018),
AvatarPromote(1019),
QuestAction(1021),
CityLevelup(1022),
UpgradeSkill(1024),
UnlockTalent(1025),
UpgradeProudSkill(1026),
PlayerLevelLimitUp(1027),
DungeonDaily(1028),
ItemGiving(1030),
ForgeCost(1031),
InvestigationReward(1032),
InvestigationTargetReward(1033),
GadgetInteract(1034),
SeaLampCiMaterial(1036),
SeaLampContributionReward(1037),
SeaLampPhaseReward(1038),
SeaLampFlyLamp(1039),
AutoRecover(1040),
ActivityExpireItem(1041),
SubCoinNegative(1042),
BargainDeduct(1043),
BattlePassPaidReward(1044),
BattlePassLevelReward(1045),
TrialAvatarActivityFirstPassReward(1046),
BuyBattlePassLevel(1047),
GrantBirthdayBenefit(1048),
AchievementReward(1049),
AchievementGoalReward(1050),
FirstShareToSocialNetwork(1051),
DestroyMaterial(1052),
CodexLevelupReward(1053),
HuntingOfferReward(1054),
UseWidgetAnchorPoint(1055),
UseWidgetBonfire(1056),
UngradeWeaponReturnMaterial(1057),
UseWidgetOneoffGatherPointDetector(1058),
UseWidgetClientCollector(1059),
UseWidgetClientDetector(1060),
TakeGeneralReward(1061),
AsterTakeSpecialReward(1062),
RemoveCodexBook(1063),
OfferingItem(1064),
UseWidgetGadgetBuilder(1065),
EffigyFirstPassReward(1066),
EffigyReward(1067),
ReunionFirstGiftReward(1068),
ReunionSignInReward(1069),
ReunionWatcherReward(1070),
SalesmanMpReward(1071),
ActionReasionAvatarPromoteReward(1072),
BlessingRedeemReward(1073),
ActionMiracleRingReward(1074),
ExpeditionReward(1075),
TreasureMapRemoveDetector(1076),
MechanicusDungeonTicket(1077),
MechanicusLevelupGear(1078),
MechanicusBattleSettle(1079),
RegionSearchReward(1080),
UnlockCoopChapter(1081),
TakeCoopReward(1082),
FleurFairDungeonReward(1083),
ActivityScore(1084),
ChannellerSlabOneoffDungeonReward(1085),
FurnitureMakeStart(1086),
FurnitureMakeTake(1087),
FurnitureMakeCancel(1088),
FurnitureMakeFastFinish(1089),
ChannellerSlabLoopDungeonFirstPassReward(1090),
ChannellerSlabLoopDungeonScoreReward(1091),
HomeLimitedShopBuy(1092),
HomeCoinCollect(1093);
private static final Int2ObjectMap<ActionReason> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, ActionReason> stringMap = new HashMap<>();
static {
Stream.of(values())
.forEach(
e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private final int value;
ActionReason(int value) {
this.value = value;
}
public static ActionReason getTypeByValue(int value) {
return map.getOrDefault(value, None);
}
public static ActionReason getTypeByName(String name) {
return stringMap.getOrDefault(name, None);
}
public int getValue() {
return value;
}
}

View File

@@ -1,18 +1,18 @@
package emu.grasscutter.game.props;
public enum BattlePassMissionRefreshType {
BATTLE_PASS_MISSION_REFRESH_DAILY(0),
BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE(1), // Weekly
BATTLE_PASS_MISSION_REFRESH_SCHEDULE(2), // Per BP
BATTLE_PASS_MISSION_REFRESH_CYCLE(1); // Event?
private final int value;
BattlePassMissionRefreshType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
package emu.grasscutter.game.props;
public enum BattlePassMissionRefreshType {
BATTLE_PASS_MISSION_REFRESH_DAILY(0),
BATTLE_PASS_MISSION_REFRESH_CYCLE_CROSS_SCHEDULE(1), // Weekly
BATTLE_PASS_MISSION_REFRESH_SCHEDULE(2), // Per BP
BATTLE_PASS_MISSION_REFRESH_CYCLE(1); // Event?
private final int value;
BattlePassMissionRefreshType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}

View File

@@ -1,26 +1,26 @@
package emu.grasscutter.game.props;
import emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission.MissionStatus;
public enum BattlePassMissionStatus {
MISSION_STATUS_INVALID(0, MissionStatus.MISSION_STATUS_INVALID),
MISSION_STATUS_UNFINISHED(1, MissionStatus.MISSION_STATUS_UNFINISHED),
MISSION_STATUS_FINISHED(2, MissionStatus.MISSION_STATUS_FINISHED),
MISSION_STATUS_POINT_TAKEN(3, MissionStatus.MISSION_STATUS_POINT_TAKEN);
private final int value;
private final MissionStatus missionStatus;
BattlePassMissionStatus(int value, MissionStatus missionStatus) {
this.value = value;
this.missionStatus = missionStatus; // In case proto enum values change later
}
public int getValue() {
return value;
}
public MissionStatus getMissionStatus() {
return missionStatus;
}
}
package emu.grasscutter.game.props;
import emu.grasscutter.net.proto.BattlePassMissionOuterClass.BattlePassMission.MissionStatus;
public enum BattlePassMissionStatus {
MISSION_STATUS_INVALID(0, MissionStatus.MISSION_STATUS_INVALID),
MISSION_STATUS_UNFINISHED(1, MissionStatus.MISSION_STATUS_UNFINISHED),
MISSION_STATUS_FINISHED(2, MissionStatus.MISSION_STATUS_FINISHED),
MISSION_STATUS_POINT_TAKEN(3, MissionStatus.MISSION_STATUS_POINT_TAKEN);
private final int value;
private final MissionStatus missionStatus;
BattlePassMissionStatus(int value, MissionStatus missionStatus) {
this.value = value;
this.missionStatus = missionStatus; // In case proto enum values change later
}
public int getValue() {
return value;
}
public MissionStatus getMissionStatus() {
return missionStatus;
}
}

View File

@@ -1,56 +1,56 @@
package emu.grasscutter.game.props;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public enum ClimateType {
CLIMATE_NONE(0),
CLIMATE_SUNNY(1),
CLIMATE_CLOUDY(2),
CLIMATE_RAIN(3),
CLIMATE_THUNDERSTORM(4),
CLIMATE_SNOW(5),
CLIMATE_MIST(6);
private static final Int2ObjectMap<ClimateType> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, ClimateType> stringMap = new HashMap<>();
static {
Stream.of(values())
.forEach(
e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private final int value;
ClimateType(int value) {
this.value = value;
}
public static ClimateType getTypeByValue(int value) {
return map.getOrDefault(value, CLIMATE_NONE);
}
public static ClimateType getTypeByName(String name) {
return stringMap.getOrDefault(name, CLIMATE_NONE);
}
public static ClimateType getTypeByShortName(String shortName) {
String name = "CLIMATE_" + shortName.toUpperCase();
return stringMap.getOrDefault(name, CLIMATE_NONE);
}
public int getValue() {
return this.value;
}
public String getShortName() {
return this.name().substring(8).toLowerCase();
}
}
package emu.grasscutter.game.props;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public enum ClimateType {
CLIMATE_NONE(0),
CLIMATE_SUNNY(1),
CLIMATE_CLOUDY(2),
CLIMATE_RAIN(3),
CLIMATE_THUNDERSTORM(4),
CLIMATE_SNOW(5),
CLIMATE_MIST(6);
private static final Int2ObjectMap<ClimateType> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, ClimateType> stringMap = new HashMap<>();
static {
Stream.of(values())
.forEach(
e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private final int value;
ClimateType(int value) {
this.value = value;
}
public static ClimateType getTypeByValue(int value) {
return map.getOrDefault(value, CLIMATE_NONE);
}
public static ClimateType getTypeByName(String name) {
return stringMap.getOrDefault(name, CLIMATE_NONE);
}
public static ClimateType getTypeByShortName(String shortName) {
String name = "CLIMATE_" + shortName.toUpperCase();
return stringMap.getOrDefault(name, CLIMATE_NONE);
}
public int getValue() {
return this.value;
}
public String getShortName() {
return this.name().substring(8).toLowerCase();
}
}

View File

@@ -1,72 +1,72 @@
package emu.grasscutter.game.props;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public enum EnterReason {
None(0),
Login(1),
DungeonReplay(11),
DungeonReviveOnWaypoint(12),
DungeonEnter(13),
DungeonQuit(14),
Gm(21),
QuestRollback(31),
Revival(32),
PersonalScene(41),
TransPoint(42),
ClientTransmit(43),
ForceDragBack(44),
TeamKick(51),
TeamJoin(52),
TeamBack(53),
Muip(54),
DungeonInviteAccept(55),
Lua(56),
ActivityLoadTerrain(57),
HostFromSingleToMp(58),
MpPlay(59),
AnchorPoint(60),
LuaSkipUi(61),
ReloadTerrain(62),
DraftTransfer(63),
EnterHome(64),
ExitHome(65),
ChangeHomeModule(66),
Gallery(67),
HomeSceneJump(68),
HideAndSeek(69);
private static final Int2ObjectMap<EnterReason> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, EnterReason> stringMap = new HashMap<>();
static {
Stream.of(values())
.forEach(
e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private final int value;
EnterReason(int value) {
this.value = value;
}
public static EnterReason getTypeByValue(int value) {
return map.getOrDefault(value, None);
}
public static EnterReason getTypeByName(String name) {
return stringMap.getOrDefault(name, None);
}
public int getValue() {
return value;
}
}
package emu.grasscutter.game.props;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public enum EnterReason {
None(0),
Login(1),
DungeonReplay(11),
DungeonReviveOnWaypoint(12),
DungeonEnter(13),
DungeonQuit(14),
Gm(21),
QuestRollback(31),
Revival(32),
PersonalScene(41),
TransPoint(42),
ClientTransmit(43),
ForceDragBack(44),
TeamKick(51),
TeamJoin(52),
TeamBack(53),
Muip(54),
DungeonInviteAccept(55),
Lua(56),
ActivityLoadTerrain(57),
HostFromSingleToMp(58),
MpPlay(59),
AnchorPoint(60),
LuaSkipUi(61),
ReloadTerrain(62),
DraftTransfer(63),
EnterHome(64),
ExitHome(65),
ChangeHomeModule(66),
Gallery(67),
HomeSceneJump(68),
HideAndSeek(69);
private static final Int2ObjectMap<EnterReason> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, EnterReason> stringMap = new HashMap<>();
static {
Stream.of(values())
.forEach(
e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private final int value;
EnterReason(int value) {
this.value = value;
}
public static EnterReason getTypeByValue(int value) {
return map.getOrDefault(value, None);
}
public static EnterReason getTypeByName(String name) {
return stringMap.getOrDefault(name, None);
}
public int getValue() {
return value;
}
}

View File

@@ -1,22 +1,22 @@
package emu.grasscutter.game.props;
public enum EntityIdType {
AVATAR(0x01),
MONSTER(0x02),
NPC(0x03),
GADGET(0x04),
REGION(0x05),
WEAPON(0x06),
TEAM(0x09),
MPLEVEL(0x0b);
private final int id;
EntityIdType(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
package emu.grasscutter.game.props;
public enum EntityIdType {
AVATAR(0x01),
MONSTER(0x02),
NPC(0x03),
GADGET(0x04),
REGION(0x05),
WEAPON(0x06),
TEAM(0x09),
MPLEVEL(0x0b);
private final int id;
EntityIdType(int id) {
this.id = id;
}
public int getId() {
return id;
}
}

View File

@@ -1,44 +1,44 @@
package emu.grasscutter.game.props;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public enum FetterState {
NONE(0),
NOT_OPEN(1),
OPEN(1),
FINISH(3);
private static final Int2ObjectMap<FetterState> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, FetterState> stringMap = new HashMap<>();
static {
Stream.of(values())
.forEach(
e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private final int value;
FetterState(int value) {
this.value = value;
}
public static FetterState getTypeByValue(int value) {
return map.getOrDefault(value, NONE);
}
public static FetterState getTypeByName(String name) {
return stringMap.getOrDefault(name, NONE);
}
public int getValue() {
return value;
}
}
package emu.grasscutter.game.props;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public enum FetterState {
NONE(0),
NOT_OPEN(1),
OPEN(1),
FINISH(3);
private static final Int2ObjectMap<FetterState> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, FetterState> stringMap = new HashMap<>();
static {
Stream.of(values())
.forEach(
e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private final int value;
FetterState(int value) {
this.value = value;
}
public static FetterState getTypeByValue(int value) {
return map.getOrDefault(value, NONE);
}
public static FetterState getTypeByName(String name) {
return stringMap.getOrDefault(name, NONE);
}
public int getValue() {
return value;
}
}

View File

@@ -1,105 +1,105 @@
package emu.grasscutter.game.props;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public enum GrowCurve {
GROW_CURVE_NONE(0),
GROW_CURVE_HP(1),
GROW_CURVE_ATTACK(2),
GROW_CURVE_STAMINA(3),
GROW_CURVE_STRIKE(4),
GROW_CURVE_ANTI_STRIKE(5),
GROW_CURVE_ANTI_STRIKE1(6),
GROW_CURVE_ANTI_STRIKE2(7),
GROW_CURVE_ANTI_STRIKE3(8),
GROW_CURVE_STRIKE_HURT(9),
GROW_CURVE_ELEMENT(10),
GROW_CURVE_KILL_EXP(11),
GROW_CURVE_DEFENSE(12),
GROW_CURVE_ATTACK_BOMB(13),
GROW_CURVE_HP_LITTLEMONSTER(14),
GROW_CURVE_ELEMENT_MASTERY(15),
GROW_CURVE_PROGRESSION(16),
GROW_CURVE_DEFENDING(17),
GROW_CURVE_MHP(18),
GROW_CURVE_MATK(19),
GROW_CURVE_TOWERATK(20),
GROW_CURVE_HP_S5(21),
GROW_CURVE_HP_S4(22),
GROW_CURVE_HP_2(23),
GROW_CURVE_ATTACK_S5(31),
GROW_CURVE_ATTACK_S4(32),
GROW_CURVE_ATTACK_S3(33),
GROW_CURVE_STRIKE_S5(34),
GROW_CURVE_DEFENSE_S5(41),
GROW_CURVE_DEFENSE_S4(42),
GROW_CURVE_ATTACK_101(1101),
GROW_CURVE_ATTACK_102(1102),
GROW_CURVE_ATTACK_103(1103),
GROW_CURVE_ATTACK_104(1104),
GROW_CURVE_ATTACK_105(1105),
GROW_CURVE_ATTACK_201(1201),
GROW_CURVE_ATTACK_202(1202),
GROW_CURVE_ATTACK_203(1203),
GROW_CURVE_ATTACK_204(1204),
GROW_CURVE_ATTACK_205(1205),
GROW_CURVE_ATTACK_301(1301),
GROW_CURVE_ATTACK_302(1302),
GROW_CURVE_ATTACK_303(1303),
GROW_CURVE_ATTACK_304(1304),
GROW_CURVE_ATTACK_305(1305),
GROW_CURVE_CRITICAL_101(2101),
GROW_CURVE_CRITICAL_102(2102),
GROW_CURVE_CRITICAL_103(2103),
GROW_CURVE_CRITICAL_104(2104),
GROW_CURVE_CRITICAL_105(2105),
GROW_CURVE_CRITICAL_201(2201),
GROW_CURVE_CRITICAL_202(2202),
GROW_CURVE_CRITICAL_203(2203),
GROW_CURVE_CRITICAL_204(2204),
GROW_CURVE_CRITICAL_205(2205),
GROW_CURVE_CRITICAL_301(2301),
GROW_CURVE_CRITICAL_302(2302),
GROW_CURVE_CRITICAL_303(2303),
GROW_CURVE_CRITICAL_304(2304),
GROW_CURVE_CRITICAL_305(2305);
public static final int[] fightProps =
new int[] {
1, 4, 7, 20, 21, 22, 23, 26, 27, 28, 29, 30, 40, 41, 42, 43, 44, 45, 46, 50, 51, 52, 53, 54,
55, 56, 2000, 2001, 2002, 2003, 1010
};
private static final Int2ObjectMap<GrowCurve> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, GrowCurve> stringMap = new HashMap<>();
static {
Stream.of(values())
.forEach(
e -> {
map.put(e.getId(), e);
stringMap.put(e.name(), e);
});
}
private final int id;
GrowCurve(int id) {
this.id = id;
}
public static GrowCurve getPropById(int value) {
return map.getOrDefault(value, GROW_CURVE_NONE);
}
public static GrowCurve getPropByName(String name) {
return stringMap.getOrDefault(name, GROW_CURVE_NONE);
}
public int getId() {
return id;
}
}
package emu.grasscutter.game.props;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public enum GrowCurve {
GROW_CURVE_NONE(0),
GROW_CURVE_HP(1),
GROW_CURVE_ATTACK(2),
GROW_CURVE_STAMINA(3),
GROW_CURVE_STRIKE(4),
GROW_CURVE_ANTI_STRIKE(5),
GROW_CURVE_ANTI_STRIKE1(6),
GROW_CURVE_ANTI_STRIKE2(7),
GROW_CURVE_ANTI_STRIKE3(8),
GROW_CURVE_STRIKE_HURT(9),
GROW_CURVE_ELEMENT(10),
GROW_CURVE_KILL_EXP(11),
GROW_CURVE_DEFENSE(12),
GROW_CURVE_ATTACK_BOMB(13),
GROW_CURVE_HP_LITTLEMONSTER(14),
GROW_CURVE_ELEMENT_MASTERY(15),
GROW_CURVE_PROGRESSION(16),
GROW_CURVE_DEFENDING(17),
GROW_CURVE_MHP(18),
GROW_CURVE_MATK(19),
GROW_CURVE_TOWERATK(20),
GROW_CURVE_HP_S5(21),
GROW_CURVE_HP_S4(22),
GROW_CURVE_HP_2(23),
GROW_CURVE_ATTACK_S5(31),
GROW_CURVE_ATTACK_S4(32),
GROW_CURVE_ATTACK_S3(33),
GROW_CURVE_STRIKE_S5(34),
GROW_CURVE_DEFENSE_S5(41),
GROW_CURVE_DEFENSE_S4(42),
GROW_CURVE_ATTACK_101(1101),
GROW_CURVE_ATTACK_102(1102),
GROW_CURVE_ATTACK_103(1103),
GROW_CURVE_ATTACK_104(1104),
GROW_CURVE_ATTACK_105(1105),
GROW_CURVE_ATTACK_201(1201),
GROW_CURVE_ATTACK_202(1202),
GROW_CURVE_ATTACK_203(1203),
GROW_CURVE_ATTACK_204(1204),
GROW_CURVE_ATTACK_205(1205),
GROW_CURVE_ATTACK_301(1301),
GROW_CURVE_ATTACK_302(1302),
GROW_CURVE_ATTACK_303(1303),
GROW_CURVE_ATTACK_304(1304),
GROW_CURVE_ATTACK_305(1305),
GROW_CURVE_CRITICAL_101(2101),
GROW_CURVE_CRITICAL_102(2102),
GROW_CURVE_CRITICAL_103(2103),
GROW_CURVE_CRITICAL_104(2104),
GROW_CURVE_CRITICAL_105(2105),
GROW_CURVE_CRITICAL_201(2201),
GROW_CURVE_CRITICAL_202(2202),
GROW_CURVE_CRITICAL_203(2203),
GROW_CURVE_CRITICAL_204(2204),
GROW_CURVE_CRITICAL_205(2205),
GROW_CURVE_CRITICAL_301(2301),
GROW_CURVE_CRITICAL_302(2302),
GROW_CURVE_CRITICAL_303(2303),
GROW_CURVE_CRITICAL_304(2304),
GROW_CURVE_CRITICAL_305(2305);
public static final int[] fightProps =
new int[] {
1, 4, 7, 20, 21, 22, 23, 26, 27, 28, 29, 30, 40, 41, 42, 43, 44, 45, 46, 50, 51, 52, 53, 54,
55, 56, 2000, 2001, 2002, 2003, 1010
};
private static final Int2ObjectMap<GrowCurve> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, GrowCurve> stringMap = new HashMap<>();
static {
Stream.of(values())
.forEach(
e -> {
map.put(e.getId(), e);
stringMap.put(e.name(), e);
});
}
private final int id;
GrowCurve(int id) {
this.id = id;
}
public static GrowCurve getPropById(int value) {
return map.getOrDefault(value, GROW_CURVE_NONE);
}
public static GrowCurve getPropByName(String name) {
return stringMap.getOrDefault(name, GROW_CURVE_NONE);
}
public int getId() {
return id;
}
}

View File

@@ -1,19 +1,19 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAcceptQuest extends ItemUseInt {
public ItemUseAcceptQuest(String[] useParam) {
super(useParam);
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ACCEPT_QUEST;
}
@Override
public boolean useItem(UseItemParams params) {
return (params.player.getQuestManager().addQuest(this.i) != null);
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAcceptQuest extends ItemUseInt {
public ItemUseAcceptQuest(String[] useParam) {
super(useParam);
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ACCEPT_QUEST;
}
@Override
public boolean useItem(UseItemParams params) {
return (params.player.getQuestManager().addQuest(this.i) != null);
}
}

View File

@@ -1,83 +1,83 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.data.common.ItemUseData;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAction {
public static ItemUseAction fromItemUseData(ItemUseData data) {
var useParam = data.getUseParam();
return switch (data.getUseOp()) {
case ITEM_USE_NONE -> null;
// Uprade materials - no direct usage
case ITEM_USE_ADD_EXP -> new ItemUseAddExp(useParam);
case ITEM_USE_ADD_RELIQUARY_EXP -> new ItemUseAddReliquaryExp(useParam);
case ITEM_USE_ADD_WEAPON_EXP -> new ItemUseAddWeaponExp(useParam);
// Energy pickups
case ITEM_USE_ADD_ALL_ENERGY -> new ItemUseAddAllEnergy(useParam);
case ITEM_USE_ADD_ELEM_ENERGY -> new ItemUseAddElemEnergy(useParam);
// Give items
case ITEM_USE_ADD_ITEM -> new ItemUseAddItem(useParam);
case ITEM_USE_GAIN_AVATAR -> new ItemUseGainAvatar(useParam);
case ITEM_USE_GAIN_COSTUME -> new ItemUseGainCostume(useParam); // TODO - real success/fail
case ITEM_USE_GAIN_FLYCLOAK -> new ItemUseGainFlycloak(useParam); // TODO - real success/fail
case ITEM_USE_GAIN_NAME_CARD -> new ItemUseGainNameCard(useParam);
case ITEM_USE_CHEST_SELECT_ITEM -> new ItemUseChestSelectItem(useParam);
case ITEM_USE_ADD_SELECT_ITEM -> new ItemUseAddSelectItem(useParam);
case ITEM_USE_GRANT_SELECT_REWARD -> new ItemUseGrantSelectReward(useParam);
case ITEM_USE_COMBINE_ITEM -> new ItemUseCombineItem(useParam);
case ITEM_USE_OPEN_RANDOM_CHEST -> new ItemUseOpenRandomChest(useParam);
// Food effects
case ITEM_USE_RELIVE_AVATAR -> new ItemUseReliveAvatar(
useParam); // First action for revival food. Should we worry about race conditions in
// parallel streams?
case ITEM_USE_ADD_CUR_HP -> new ItemUseAddCurHp(useParam);
case ITEM_USE_ADD_CUR_STAMINA -> new ItemUseAddCurStamina(useParam);
case ITEM_USE_ADD_SERVER_BUFF -> new ItemUseAddServerBuff(useParam);
case ITEM_USE_MAKE_GADGET -> new ItemUseMakeGadget(useParam);
// Unlock recipes - TODO: allow scheduling packets for after recipe is removed
case ITEM_USE_UNLOCK_COMBINE -> new ItemUseUnlockCombine(useParam);
case ITEM_USE_UNLOCK_CODEX -> new ItemUseUnlockCodex(
useParam); // TODO: No backend for this yet
case ITEM_USE_UNLOCK_COOK_RECIPE -> new ItemUseUnlockCookRecipe(useParam);
case ITEM_USE_UNLOCK_FORGE -> new ItemUseUnlockForge(useParam);
case ITEM_USE_UNLOCK_FURNITURE_FORMULA -> new ItemUseUnlockFurnitureFormula(useParam);
case ITEM_USE_UNLOCK_FURNITURE_SUITE -> new ItemUseUnlockFurnitureSuite(useParam);
case ITEM_USE_UNLOCK_HOME_MODULE -> new ItemUseUnlockHomeModule(
useParam); // No backend for this yet
case ITEM_USE_UNLOCK_HOME_BGM -> new ItemUseUnlockHomeBgm(useParam);
// Account things
case ITEM_USE_ACCEPT_QUEST -> new ItemUseAcceptQuest(useParam);
case ITEM_USE_GAIN_CARD_PRODUCT -> new ItemUseGainCardProduct(useParam);
case ITEM_USE_UNLOCK_PAID_BATTLE_PASS_NORMAL -> new ItemUseUnlockPaidBattlePassNormal(
useParam); // TODO: add paid BP
// Unused in current resources
case ITEM_USE_DEL_SERVER_BUFF -> null;
case ITEM_USE_ADD_BIG_TALENT_POINT -> null;
case ITEM_USE_GAIN_RESIN_CARD_PRODUCT -> null;
case ITEM_USE_TRIGGER_ABILITY -> null;
case ITEM_USE_ADD_TREASURE_MAP_BONUS_REGION_FRAGMENT -> null;
// Used in current resources but no point yet
case ITEM_USE_ADD_PERSIST_STAMINA -> null; // [int amount] one Test item
case ITEM_USE_ADD_TEMPORARY_STAMINA -> null; // [int amount] one Test item
case ITEM_USE_ADD_DUNGEON_COND_TIME -> null; // [int 1, int 15 or 20] - minigame shards
case ITEM_USE_ADD_CHANNELLER_SLAB_BUFF -> null; // [int] minigame buffs
case ITEM_USE_ADD_REGIONAL_PLAY_VAR -> null; // [String, int] - coral butterfly effect
};
}
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_NONE;
}
public boolean useItem(UseItemParams params) {
// An item must return true on at least one of its actions to count as successfully used.
// If all of the actions return false, the item will not be consumed from inventory.
return false;
}
public boolean postUseItem(UseItemParams params) {
// This is run after the item has been consumed from inventory.
return false;
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.data.common.ItemUseData;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAction {
public static ItemUseAction fromItemUseData(ItemUseData data) {
var useParam = data.getUseParam();
return switch (data.getUseOp()) {
case ITEM_USE_NONE -> null;
// Uprade materials - no direct usage
case ITEM_USE_ADD_EXP -> new ItemUseAddExp(useParam);
case ITEM_USE_ADD_RELIQUARY_EXP -> new ItemUseAddReliquaryExp(useParam);
case ITEM_USE_ADD_WEAPON_EXP -> new ItemUseAddWeaponExp(useParam);
// Energy pickups
case ITEM_USE_ADD_ALL_ENERGY -> new ItemUseAddAllEnergy(useParam);
case ITEM_USE_ADD_ELEM_ENERGY -> new ItemUseAddElemEnergy(useParam);
// Give items
case ITEM_USE_ADD_ITEM -> new ItemUseAddItem(useParam);
case ITEM_USE_GAIN_AVATAR -> new ItemUseGainAvatar(useParam);
case ITEM_USE_GAIN_COSTUME -> new ItemUseGainCostume(useParam); // TODO - real success/fail
case ITEM_USE_GAIN_FLYCLOAK -> new ItemUseGainFlycloak(useParam); // TODO - real success/fail
case ITEM_USE_GAIN_NAME_CARD -> new ItemUseGainNameCard(useParam);
case ITEM_USE_CHEST_SELECT_ITEM -> new ItemUseChestSelectItem(useParam);
case ITEM_USE_ADD_SELECT_ITEM -> new ItemUseAddSelectItem(useParam);
case ITEM_USE_GRANT_SELECT_REWARD -> new ItemUseGrantSelectReward(useParam);
case ITEM_USE_COMBINE_ITEM -> new ItemUseCombineItem(useParam);
case ITEM_USE_OPEN_RANDOM_CHEST -> new ItemUseOpenRandomChest(useParam);
// Food effects
case ITEM_USE_RELIVE_AVATAR -> new ItemUseReliveAvatar(
useParam); // First action for revival food. Should we worry about race conditions in
// parallel streams?
case ITEM_USE_ADD_CUR_HP -> new ItemUseAddCurHp(useParam);
case ITEM_USE_ADD_CUR_STAMINA -> new ItemUseAddCurStamina(useParam);
case ITEM_USE_ADD_SERVER_BUFF -> new ItemUseAddServerBuff(useParam);
case ITEM_USE_MAKE_GADGET -> new ItemUseMakeGadget(useParam);
// Unlock recipes - TODO: allow scheduling packets for after recipe is removed
case ITEM_USE_UNLOCK_COMBINE -> new ItemUseUnlockCombine(useParam);
case ITEM_USE_UNLOCK_CODEX -> new ItemUseUnlockCodex(
useParam); // TODO: No backend for this yet
case ITEM_USE_UNLOCK_COOK_RECIPE -> new ItemUseUnlockCookRecipe(useParam);
case ITEM_USE_UNLOCK_FORGE -> new ItemUseUnlockForge(useParam);
case ITEM_USE_UNLOCK_FURNITURE_FORMULA -> new ItemUseUnlockFurnitureFormula(useParam);
case ITEM_USE_UNLOCK_FURNITURE_SUITE -> new ItemUseUnlockFurnitureSuite(useParam);
case ITEM_USE_UNLOCK_HOME_MODULE -> new ItemUseUnlockHomeModule(
useParam); // No backend for this yet
case ITEM_USE_UNLOCK_HOME_BGM -> new ItemUseUnlockHomeBgm(useParam);
// Account things
case ITEM_USE_ACCEPT_QUEST -> new ItemUseAcceptQuest(useParam);
case ITEM_USE_GAIN_CARD_PRODUCT -> new ItemUseGainCardProduct(useParam);
case ITEM_USE_UNLOCK_PAID_BATTLE_PASS_NORMAL -> new ItemUseUnlockPaidBattlePassNormal(
useParam); // TODO: add paid BP
// Unused in current resources
case ITEM_USE_DEL_SERVER_BUFF -> null;
case ITEM_USE_ADD_BIG_TALENT_POINT -> null;
case ITEM_USE_GAIN_RESIN_CARD_PRODUCT -> null;
case ITEM_USE_TRIGGER_ABILITY -> null;
case ITEM_USE_ADD_TREASURE_MAP_BONUS_REGION_FRAGMENT -> null;
// Used in current resources but no point yet
case ITEM_USE_ADD_PERSIST_STAMINA -> null; // [int amount] one Test item
case ITEM_USE_ADD_TEMPORARY_STAMINA -> null; // [int amount] one Test item
case ITEM_USE_ADD_DUNGEON_COND_TIME -> null; // [int 1, int 15 or 20] - minigame shards
case ITEM_USE_ADD_CHANNELLER_SLAB_BUFF -> null; // [int] minigame buffs
case ITEM_USE_ADD_REGIONAL_PLAY_VAR -> null; // [String, int] - coral butterfly effect
};
}
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_NONE;
}
public boolean useItem(UseItemParams params) {
// An item must return true on at least one of its actions to count as successfully used.
// If all of the actions return false, the item will not be consumed from inventory.
return false;
}
public boolean postUseItem(UseItemParams params) {
// This is run after the item has been consumed from inventory.
return false;
}
}

View File

@@ -1,24 +1,24 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddAllEnergy extends ItemUseAddEnergy {
private float energy = 0f;
public ItemUseAddAllEnergy(String[] useParam) {
try {
this.energy = Float.parseFloat(useParam[0]);
} catch (Exception ignored) {
}
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_ALL_ENERGY;
}
public float getAddEnergy(ElementType avatarElement) {
return this.energy;
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddAllEnergy extends ItemUseAddEnergy {
private float energy = 0f;
public ItemUseAddAllEnergy(String[] useParam) {
try {
this.energy = Float.parseFloat(useParam[0]);
} catch (Exception ignored) {
}
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_ALL_ENERGY;
}
public float getAddEnergy(ElementType avatarElement) {
return this.energy;
}
}

View File

@@ -1,19 +1,19 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddCurStamina extends ItemUseInt {
public ItemUseAddCurStamina(String[] useParam) {
super(useParam);
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_CUR_STAMINA;
}
@Override
public boolean useItem(UseItemParams params) {
return params.player.getStaminaManager().addCurrentStamina(this.i);
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddCurStamina extends ItemUseInt {
public ItemUseAddCurStamina(String[] useParam) {
super(useParam);
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_CUR_STAMINA;
}
@Override
public boolean useItem(UseItemParams params) {
return params.player.getStaminaManager().addCurrentStamina(this.i);
}
}

View File

@@ -1,34 +1,34 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddElemEnergy extends ItemUseAddEnergy {
private ElementType element = ElementType.None;
private float elemEnergy = 0f;
private float otherEnergy = 0f;
public ItemUseAddElemEnergy(String[] useParam) {
try {
this.element = ElementType.getTypeByValue(Integer.parseInt(useParam[0]));
} catch (Exception ignored) {
}
try {
this.elemEnergy = Float.parseFloat(useParam[1]);
} catch (Exception ignored) {
}
try {
this.otherEnergy = Float.parseFloat(useParam[2]);
} catch (Exception ignored) {
}
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_ELEM_ENERGY;
}
public float getAddEnergy(ElementType avatarElement) {
return (avatarElement == this.element) ? this.elemEnergy : this.otherEnergy;
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddElemEnergy extends ItemUseAddEnergy {
private ElementType element = ElementType.None;
private float elemEnergy = 0f;
private float otherEnergy = 0f;
public ItemUseAddElemEnergy(String[] useParam) {
try {
this.element = ElementType.getTypeByValue(Integer.parseInt(useParam[0]));
} catch (Exception ignored) {
}
try {
this.elemEnergy = Float.parseFloat(useParam[1]);
} catch (Exception ignored) {
}
try {
this.otherEnergy = Float.parseFloat(useParam[2]);
} catch (Exception ignored) {
}
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_ELEM_ENERGY;
}
public float getAddEnergy(ElementType avatarElement) {
return (avatarElement == this.element) ? this.elemEnergy : this.otherEnergy;
}
}

View File

@@ -1,18 +1,18 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddExp extends ItemUseInt {
public ItemUseAddExp(String[] useParam) {
super(useParam);
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_EXP;
}
public int getExp() {
return this.i;
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddExp extends ItemUseInt {
public ItemUseAddExp(String[] useParam) {
super(useParam);
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_EXP;
}
public int getExp() {
return this.i;
}
}

View File

@@ -1,14 +1,14 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddReliquaryExp extends ItemUseAddExp {
public ItemUseAddReliquaryExp(String[] useParam) {
super(useParam);
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_RELIQUARY_EXP;
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddReliquaryExp extends ItemUseAddExp {
public ItemUseAddReliquaryExp(String[] useParam) {
super(useParam);
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_RELIQUARY_EXP;
}
}

View File

@@ -1,22 +1,22 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddSelectItem extends ItemUseSelectItems {
public ItemUseAddSelectItem(String[] useParam) {
String[] options = useParam[0].split(",");
this.optionItemIds = new int[options.length];
for (int i = 0; i < options.length; i++) {
try {
this.optionItemIds[i] = Integer.parseInt(options[i]);
} catch (NumberFormatException ignored) {
this.optionItemIds[i] = INVALID;
}
}
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_SELECT_ITEM;
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddSelectItem extends ItemUseSelectItems {
public ItemUseAddSelectItem(String[] useParam) {
String[] options = useParam[0].split(",");
this.optionItemIds = new int[options.length];
for (int i = 0; i < options.length; i++) {
try {
this.optionItemIds[i] = Integer.parseInt(options[i]);
} catch (NumberFormatException ignored) {
this.optionItemIds[i] = INVALID;
}
}
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_SELECT_ITEM;
}
}

View File

@@ -1,25 +1,25 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddServerBuff extends ItemUseInt {
private int duration = 0;
public ItemUseAddServerBuff(String[] useParam) {
super(useParam);
try {
this.duration = Integer.parseInt(useParam[1]);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {
}
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_SERVER_BUFF;
}
@Override
public boolean useItem(UseItemParams params) {
return params.player.getBuffManager().addBuff(this.i, this.duration, params.targetAvatar);
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddServerBuff extends ItemUseInt {
private int duration = 0;
public ItemUseAddServerBuff(String[] useParam) {
super(useParam);
try {
this.duration = Integer.parseInt(useParam[1]);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {
}
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_SERVER_BUFF;
}
@Override
public boolean useItem(UseItemParams params) {
return params.player.getBuffManager().addBuff(this.i, this.duration, params.targetAvatar);
}
}

View File

@@ -1,14 +1,14 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddWeaponExp extends ItemUseAddExp {
public ItemUseAddWeaponExp(String[] useParam) {
super(useParam);
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_WEAPON_EXP;
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseAddWeaponExp extends ItemUseAddExp {
public ItemUseAddWeaponExp(String[] useParam) {
super(useParam);
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_WEAPON_EXP;
}
}

View File

@@ -1,31 +1,31 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseCombineItem extends ItemUseInt {
private int resultId = 0;
private int resultCount = 1;
public ItemUseCombineItem(String[] useParam) {
super(useParam);
try {
this.resultId = Integer.parseInt(useParam[1]);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {
}
try {
this.resultCount = Integer.parseInt(useParam[2]);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {
}
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_COMBINE_ITEM;
}
@Override
public boolean useItem(UseItemParams params) {
if (params.count != this.i) return false; // Wrong amount of fragments supplied!
return params.player.getInventory().addItem(this.resultId, this.resultCount);
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseCombineItem extends ItemUseInt {
private int resultId = 0;
private int resultCount = 1;
public ItemUseCombineItem(String[] useParam) {
super(useParam);
try {
this.resultId = Integer.parseInt(useParam[1]);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {
}
try {
this.resultCount = Integer.parseInt(useParam[2]);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {
}
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_COMBINE_ITEM;
}
@Override
public boolean useItem(UseItemParams params) {
if (params.count != this.i) return false; // Wrong amount of fragments supplied!
return params.player.getInventory().addItem(this.resultId, this.resultCount);
}
}

View File

@@ -1,51 +1,51 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.game.systems.InventorySystem;
import java.util.Optional;
public class ItemUseGainAvatar extends ItemUseInt {
private int level = 1;
private int constellation = 0;
public ItemUseGainAvatar(String[] useParam) {
super(useParam);
try {
this.level = Integer.parseInt(useParam[1]);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {
}
try {
this.constellation = Integer.parseInt(useParam[2]);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {
}
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_GAIN_AVATAR;
}
@Override
public boolean useItem(UseItemParams params) {
int haveConstellation =
InventorySystem.checkPlayerAvatarConstellationLevel(params.player, this.i);
if (haveConstellation == -2 || haveConstellation >= 6) {
return false;
} else if (haveConstellation == -1) {
var avatar = new Avatar(this.i);
avatar.setLevel(this.level);
avatar.forceConstellationLevel(this.constellation);
avatar.recalcStats();
params.player.addAvatar(avatar);
return true;
} else {
int itemId =
Optional.ofNullable(params.player.getAvatars().getAvatarById(this.i))
.map(Avatar::getSkillDepot)
.map(depot -> depot.getTalentCostItemId())
.orElse((this.i % 1000) + 100);
return params.player.getInventory().addItem(itemId);
}
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.game.systems.InventorySystem;
import java.util.Optional;
public class ItemUseGainAvatar extends ItemUseInt {
private int level = 1;
private int constellation = 0;
public ItemUseGainAvatar(String[] useParam) {
super(useParam);
try {
this.level = Integer.parseInt(useParam[1]);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {
}
try {
this.constellation = Integer.parseInt(useParam[2]);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {
}
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_GAIN_AVATAR;
}
@Override
public boolean useItem(UseItemParams params) {
int haveConstellation =
InventorySystem.checkPlayerAvatarConstellationLevel(params.player, this.i);
if (haveConstellation == -2 || haveConstellation >= 6) {
return false;
} else if (haveConstellation == -1) {
var avatar = new Avatar(this.i);
avatar.setLevel(this.level);
avatar.forceConstellationLevel(this.constellation);
avatar.recalcStats();
params.player.addAvatar(avatar);
return true;
} else {
int itemId =
Optional.ofNullable(params.player.getAvatars().getAvatarById(this.i))
.map(Avatar::getSkillDepot)
.map(depot -> depot.getTalentCostItemId())
.orElse((this.i % 1000) + 100);
return params.player.getInventory().addItem(itemId);
}
}
}

View File

@@ -1,17 +1,17 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseGainCardProduct extends ItemUseAction {
public ItemUseGainCardProduct(String[] useParam) {}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_GAIN_CARD_PRODUCT;
}
@Override
public boolean useItem(UseItemParams params) {
return params.player.rechargeMoonCard();
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseGainCardProduct extends ItemUseAction {
public ItemUseGainCardProduct(String[] useParam) {}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_GAIN_CARD_PRODUCT;
}
@Override
public boolean useItem(UseItemParams params) {
return params.player.rechargeMoonCard();
}
}

View File

@@ -1,23 +1,23 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseGainCostume extends ItemUseInt {
public ItemUseGainCostume(String[] useParam) {
super(useParam);
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_GAIN_COSTUME;
}
@Override
public boolean useItem(UseItemParams params) {
if (GameData.getAvatarCostumeDataMap().containsKey(this.i)) {
params.player.addCostume(this.i);
}
return true;
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseGainCostume extends ItemUseInt {
public ItemUseGainCostume(String[] useParam) {
super(useParam);
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_GAIN_COSTUME;
}
@Override
public boolean useItem(UseItemParams params) {
if (GameData.getAvatarCostumeDataMap().containsKey(this.i)) {
params.player.addCostume(this.i);
}
return true;
}
}

View File

@@ -1,23 +1,23 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseGainFlycloak extends ItemUseInt {
public ItemUseGainFlycloak(String[] useParam) {
super(useParam);
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_GAIN_FLYCLOAK;
}
@Override
public boolean useItem(UseItemParams params) {
if (GameData.getAvatarFlycloakDataMap().containsKey(this.i)) {
params.player.addFlycloak(this.i);
}
return true;
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseGainFlycloak extends ItemUseInt {
public ItemUseGainFlycloak(String[] useParam) {
super(useParam);
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_GAIN_FLYCLOAK;
}
@Override
public boolean useItem(UseItemParams params) {
if (GameData.getAvatarFlycloakDataMap().containsKey(this.i)) {
params.player.addFlycloak(this.i);
}
return true;
}
}

View File

@@ -1,18 +1,18 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseGainNameCard extends ItemUseAction {
public ItemUseGainNameCard(String[] useParam) {}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_GAIN_NAME_CARD;
}
@Override
public boolean useItem(UseItemParams params) {
params.player.addNameCard(params.usedItemId);
return true;
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseGainNameCard extends ItemUseAction {
public ItemUseGainNameCard(String[] useParam) {}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_GAIN_NAME_CARD;
}
@Override
public boolean useItem(UseItemParams params) {
params.player.addNameCard(params.usedItemId);
return true;
}
}

View File

@@ -1,22 +1,22 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseGrantSelectReward extends ItemUseSelectItems {
public ItemUseGrantSelectReward(String[] useParam) {
String[] options = useParam[0].split(",");
this.optionItemIds = new int[options.length];
for (int i = 0; i < options.length; i++) {
try {
this.optionItemIds[i] = Integer.parseInt(options[i]);
} catch (NumberFormatException ignored) {
this.optionItemIds[i] = INVALID;
}
}
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_SELECT_ITEM;
}
}
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.game.props.ItemUseOp;
public class ItemUseGrantSelectReward extends ItemUseSelectItems {
public ItemUseGrantSelectReward(String[] useParam) {
String[] options = useParam[0].split(",");
this.optionItemIds = new int[options.length];
for (int i = 0; i < options.length; i++) {
try {
this.optionItemIds[i] = Integer.parseInt(options[i]);
} catch (NumberFormatException ignored) {
this.optionItemIds[i] = INVALID;
}
}
}
@Override
public ItemUseOp getItemUseOp() {
return ItemUseOp.ITEM_USE_ADD_SELECT_ITEM;
}
}

View File

@@ -1,14 +1,14 @@
package emu.grasscutter.game.props.ItemUseAction;
import lombok.Getter;
public abstract class ItemUseInt extends ItemUseAction {
@Getter protected int i = 0;
public ItemUseInt(String[] useParam) {
try {
this.i = Integer.parseInt(useParam[0]);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {
}
}
}
package emu.grasscutter.game.props.ItemUseAction;
import lombok.Getter;
public abstract class ItemUseInt extends ItemUseAction {
@Getter protected int i = 0;
public ItemUseInt(String[] useParam) {
try {
this.i = Integer.parseInt(useParam[0]);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {
}
}
}

Some files were not shown because too many files have changed in this diff Show More