mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2026-04-05 07:22:25 +02:00
Format code [skip actions]
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 + "}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user