From 6148ee8742adf2cae1aef857587c4d1c40065808 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Sat, 7 May 2022 21:47:13 +0800 Subject: [PATCH 001/312] Monsters tide turn by turn && Ban User Skill && Lua functions --- .../game/entity/EntityMonster.java | 1 + .../grasscutter/game/tower/TowerManager.java | 4 +- .../emu/grasscutter/game/world/Scene.java | 8 +- .../scripts/SceneScriptManager.java | 151 +++++++++++------- .../emu/grasscutter/scripts/ScriptLib.java | 123 +++++++++++--- .../grasscutter/scripts/data/SceneGroup.java | 12 +- .../packet/send/PacketCanUseSkillNotify.java | 19 +++ src/main/resources/logback.xml | 2 + 8 files changed, 232 insertions(+), 88 deletions(-) create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketCanUseSkillNotify.java diff --git a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java index c9d0c0982..0ae6f356b 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java @@ -117,6 +117,7 @@ public class EntityMonster extends GameEntity { this.getScene().getDeadSpawnedEntities().add(getSpawnEntry()); } if (getScene().getScriptManager().isInit() && this.getGroupId() > 0) { + getScene().getScriptManager().onMonsterDie(); getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, null); } if (getScene().getChallenge() != null && getScene().getChallenge().getGroup().id == this.getGroupId()) { diff --git a/src/main/java/emu/grasscutter/game/tower/TowerManager.java b/src/main/java/emu/grasscutter/game/tower/TowerManager.java index 51f840663..409549a1f 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerManager.java @@ -7,6 +7,7 @@ import emu.grasscutter.data.def.TowerLevelData; import emu.grasscutter.game.dungeons.DungeonSettleListener; import emu.grasscutter.game.dungeons.TowerDungeonSettleListener; import emu.grasscutter.game.player.Player; +import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; import emu.grasscutter.server.packet.send.PacketTowerCurLevelRecordChangeNotify; import emu.grasscutter.server.packet.send.PacketTowerEnterLevelRsp; @@ -75,7 +76,8 @@ public class TowerManager { player.getScene().setPrevScenePoint(enterPointId); player.getSession().send(new PacketTowerEnterLevelRsp(currentFloorId, currentLevel)); - + // stop using skill + player.getSession().send(new PacketCanUseSkillNotify(false)); } public void notifyCurLevelRecordChange(){ diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 97099c9b9..82ce9139f 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -105,7 +105,13 @@ public class Scene { public GameEntity getEntityById(int id) { return this.entities.get(id); } - + + public GameEntity getEntityByConfigId(int configId) { + return this.entities.values().stream() + .filter(x -> x.getConfigId() == configId) + .findFirst() + .orElse(null); + } /** * @return the autoCloseTime */ diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index 5f6a1b7e6..b8ba800a6 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -1,19 +1,14 @@ package emu.grasscutter.scripts; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import javax.script.Bindings; import javax.script.CompiledScript; import javax.script.ScriptException; -import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaValue; import org.luaj.vm2.lib.jse.CoerceJavaToLua; @@ -23,12 +18,8 @@ import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.data.def.WorldLevelData; import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.world.Scene; import emu.grasscutter.scripts.constants.EventType; -import emu.grasscutter.scripts.constants.ScriptGadgetState; -import emu.grasscutter.scripts.constants.ScriptRegionShape; import emu.grasscutter.scripts.data.SceneBlock; import emu.grasscutter.scripts.data.SceneConfig; import emu.grasscutter.scripts.data.SceneGadget; @@ -56,7 +47,12 @@ public class SceneScriptManager { private final Int2ObjectOpenHashMap> triggers; private final Int2ObjectOpenHashMap regions; - + private SceneGroup currentGroup; + private AtomicInteger monsterAlive; + private AtomicInteger monsterTideCount; + private int monsterSceneLimit; + private ConcurrentLinkedQueue monsterOrders; + public SceneScriptManager(Scene scene) { this.scene = scene; this.scriptLib = new ScriptLib(this); @@ -222,7 +218,8 @@ public class SceneScriptManager { cs.eval(getBindings()); // Set - group.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")); + group.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")).stream() + .collect(Collectors.toMap(x -> x.config_id, y -> y)); group.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, bindings.get("gadgets")); group.triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers")); group.suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites")); @@ -235,7 +232,7 @@ public class SceneScriptManager { // Add monsters to suite TODO optimize Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); - group.monsters.forEach(m -> map.put(m.config_id, m)); + group.monsters.entrySet().forEach(m -> map.put(m.getValue().config_id, m)); group.gadgets.forEach(m -> map.put(m.config_id, m)); for (SceneSuite suite : group.suites) { @@ -323,60 +320,92 @@ public class SceneScriptManager { } public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) { - spawnMonstersInGroup(group, group.getSuiteByIndex(suiteIndex)); + this.currentGroup = group; + this.monsterSceneLimit = 0; + var suite = group.getSuiteByIndex(suiteIndex); + if(suite == null){ + return; + } + suite.sceneMonsters.forEach(mob -> spawnMonstersInGroup(group, mob)); } public void spawnMonstersInGroup(SceneGroup group) { - spawnMonstersInGroup(group, null); + this.currentGroup = group; + this.monsterSceneLimit = 0; + group.monsters.values().forEach(mob -> spawnMonstersInGroup(group, mob)); } - - public void spawnMonstersInGroup(SceneGroup group, SceneSuite suite) { - List monsters = group.monsters; - - if (suite != null) { - monsters = suite.sceneMonsters; + public void spawnMonstersInGroup(SceneGroup group,Integer[] ordersConfigId, int tideCount, int sceneLimit) { + this.currentGroup = group; + this.monsterSceneLimit = sceneLimit; + this.monsterTideCount = new AtomicInteger(tideCount); + this.monsterAlive = new AtomicInteger(0); + this.monsterOrders = new ConcurrentLinkedQueue<>(List.of(ordersConfigId)); + + // add the last turn + group.monsters.keySet().stream() + .filter(i -> !this.monsterOrders.contains(i)) + .forEach(this.monsterOrders::add); + for (int i = 0; i < sceneLimit; i++) { + spawnMonstersInGroup(group, group.monsters.get(this.monsterOrders.poll())); + } + } + public void spawnMonstersInGroup(SceneGroup group, SceneMonster monster) { + if(monster == null){ + return; + } + if(this.monsterSceneLimit > 0){ + this.monsterTideCount.decrementAndGet(); + this.monsterAlive.incrementAndGet(); } - List toAdd = new ArrayList<>(); - - for (SceneMonster monster : monsters) { - MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); - - if (data == null) { - continue; - } - - // Calculate level - int level = monster.level; - - if (getScene().getDungeonData() != null) { - level = getScene().getDungeonData().getShowLevel(); - } else if (getScene().getWorld().getWorldLevel() > 0) { - WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel()); - - if (worldLevelData != null) { - level = worldLevelData.getMonsterLevel(); - } - } - - // Spawn mob - EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level); - entity.getRotation().set(monster.rot); - entity.setGroupId(group.id); - entity.setConfigId(monster.config_id); - - toAdd.add(entity); + MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); + + if (data == null) { + return; } - - if (toAdd.size() > 0) { - getScene().addEntities(toAdd); - - for (GameEntity entity : toAdd) { - callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId())); + + // Calculate level + int level = monster.level; + + if (getScene().getDungeonData() != null) { + level = getScene().getDungeonData().getShowLevel(); + } else if (getScene().getWorld().getWorldLevel() > 0) { + WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel()); + + if (worldLevelData != null) { + level = worldLevelData.getMonsterLevel(); + } + } + + // Spawn mob + EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level); + entity.getRotation().set(monster.rot); + entity.setGroupId(group.id); + entity.setConfigId(monster.config_id); + + getScene().addEntity(entity); + + callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId())); + } + + public void onMonsterDie(){ + if(this.monsterSceneLimit <= 0){ + return; + } + if(this.monsterAlive.decrementAndGet() >= this.monsterSceneLimit) { + // maybe not happen + return; + } + if(this.monsterTideCount.get() > 0){ + // add more + spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); + }else if(this.monsterAlive.get() == 0){ + // spawn the last turn of monsters + while(!this.monsterOrders.isEmpty()){ + spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); } } } - // Events public void callEvent(int eventType, ScriptArgs params) { @@ -405,4 +434,8 @@ public class SceneScriptManager { } } } + +// public LuaValue safetyCall(){ +// +// } } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index 941b00b60..1b9badc11 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -1,28 +1,24 @@ package emu.grasscutter.scripts; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.luaj.vm2.LuaTable; -import org.luaj.vm2.LuaValue; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.game.dungeons.DungeonChallenge; import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.data.SceneGroup; -import emu.grasscutter.scripts.data.SceneMonster; import emu.grasscutter.scripts.data.SceneRegion; -import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; import emu.grasscutter.server.packet.send.PacketGadgetStateNotify; import emu.grasscutter.server.packet.send.PacketWorktopOptionNotify; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Optional; public class ScriptLib { + public static final Logger logger = LoggerFactory.getLogger(ScriptLib.class); private final SceneScriptManager sceneScriptManager; public ScriptLib(SceneScriptManager sceneScriptManager) { @@ -34,6 +30,8 @@ public class ScriptLib { } public int SetGadgetStateByConfigId(int configId, int gadgetState) { + logger.debug("[LUA] Call SetGadgetStateByConfigId with {},{}", + configId,gadgetState); Optional entity = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getConfigId() == configId).findFirst(); @@ -53,6 +51,8 @@ public class ScriptLib { } public int SetGroupGadgetStateByConfigId(int groupId, int configId, int gadgetState) { + logger.debug("[LUA] Call SetGroupGadgetStateByConfigId with {},{},{}", + groupId,configId,gadgetState); List list = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getGroupId() == groupId).toList(); @@ -71,6 +71,8 @@ public class ScriptLib { } public int SetWorktopOptionsByGroupId(int groupId, int configId, int[] options) { + logger.debug("[LUA] Call SetWorktopOptionsByGroupId with {},{},{}", + groupId,configId,options); Optional entity = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst(); @@ -90,6 +92,8 @@ public class ScriptLib { } public int DelWorktopOptionByGroupId(int groupId, int configId, int option) { + logger.debug("[LUA] Call DelWorktopOptionByGroupId with {},{},{}",groupId,configId,option); + Optional entity = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst(); @@ -109,20 +113,24 @@ public class ScriptLib { } // Some fields are guessed - public int AutoMonsterTide(int challengeIndex, int groupId, int[] config_ids, int param4, int param5, int param6) { + public int AutoMonsterTide(int challengeIndex, int groupId, Integer[] ordersConfigId, int tideCount, int sceneLimit, int param6) { + logger.debug("[LUA] Call AutoMonsterTide with {},{},{},{},{},{}", + challengeIndex,groupId,ordersConfigId,tideCount,sceneLimit,param6); + SceneGroup group = getSceneScriptManager().getGroupById(groupId); if (group == null || group.monsters == null) { return 1; } - - // TODO just spawn all from group for now - this.getSceneScriptManager().spawnMonstersInGroup(group); + + this.getSceneScriptManager().spawnMonstersInGroup(group, ordersConfigId, tideCount, sceneLimit); return 0; } public int AddExtraGroupSuite(int groupId, int suite) { + logger.debug("[LUA] Call AddExtraGroupSuite with {},{}", + groupId,suite); SceneGroup group = getSceneScriptManager().getGroupById(groupId); if (group == null || group.monsters == null) { @@ -136,8 +144,17 @@ public class ScriptLib { } // param3 (probably time limit for timed dungeons) - public int ActiveChallenge(int challengeId, int challengeIndex, int param3, int groupId, int objectiveKills, int param5) { + public int ActiveChallenge(int challengeId, int challengeIndex, int timeLimitOrGroupId, int groupId, int objectiveKills, int param5) { + logger.debug("[LUA] Call ActiveChallenge with {},{},{},{},{},{}", + challengeId,challengeIndex,timeLimitOrGroupId,groupId,objectiveKills,param5); + SceneGroup group = getSceneScriptManager().getGroupById(groupId); + var objective = objectiveKills; + + if(group == null){ + group = getSceneScriptManager().getGroupById(timeLimitOrGroupId); + objective = groupId; + } if (group == null || group.monsters == null) { return 1; @@ -146,7 +163,7 @@ public class ScriptLib { DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(), group); challenge.setChallengeId(challengeId); challenge.setChallengeIndex(challengeIndex); - challenge.setObjective(objectiveKills); + challenge.setObjective(objective); getSceneScriptManager().getScene().setChallenge(challenge); @@ -155,26 +172,37 @@ public class ScriptLib { } public int GetGroupMonsterCountByGroupId(int groupId) { + logger.debug("[LUA] Call GetGroupMonsterCountByGroupId with {}", + groupId); return (int) getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e instanceof EntityMonster && e.getGroupId() == groupId) .count(); } public int GetGroupVariableValue(String var) { + logger.debug("[LUA] Call GetGroupVariableValue with {}", + var); return getSceneScriptManager().getVariables().getOrDefault(var, 0); } public int SetGroupVariableValue(String var, int value) { + logger.debug("[LUA] Call SetGroupVariableValue with {},{}", + var, value); getSceneScriptManager().getVariables().put(var, value); return 0; } public LuaValue ChangeGroupVariableValue(String var, int value) { + logger.debug("[LUA] Call ChangeGroupVariableValue with {},{}", + var, value); + getSceneScriptManager().getVariables().put(var, getSceneScriptManager().getVariables().get(var) + value); return LuaValue.ZERO; } public int RefreshGroup(LuaTable table) { + logger.debug("[LUA] Call RefreshGroup with {}", + table); // Kill and Respawn? int groupId = table.get("group_id").toint(); int suite = table.get("suite").toint(); @@ -192,6 +220,8 @@ public class ScriptLib { } public int GetRegionEntityCount(LuaTable table) { + logger.debug("[LUA] Call GetRegionEntityCount with {}", + table); int regionId = table.get("region_eid").toint(); int entityType = table.get("entity_type").toint(); @@ -205,21 +235,68 @@ public class ScriptLib { } public void PrintContextLog(String msg) { - Grasscutter.getLogger().info("[LUA] " + msg); + logger.info("[LUA] " + msg); } - public int TowerCountTimeStatus(int var1, int var2){ + public int TowerCountTimeStatus(int isDone, int var2){ + logger.debug("[LUA] Call TowerCountTimeStatus with {},{}", + isDone,var2); + // TODO record time return 0; } public int GetGroupMonsterCount(int var1){ - // Maybe... - return GetGroupMonsterCountByGroupId(var1); + logger.debug("[LUA] Call GetGroupMonsterCount with {}", + var1); + + return (int) getSceneScriptManager().getScene().getEntities().values().stream() + .filter(e -> e instanceof EntityMonster) + .count(); } public int SetMonsterBattleByGroup(int var1, int var2, int var3){ + logger.debug("[LUA] Call SetMonsterBattleByGroup with {},{},{}", + var1,var2,var3); + return 0; } public int CauseDungeonFail(int var1){ + logger.debug("[LUA] Call CauseDungeonFail with {}", + var1); + return 0; } + // 8-1 + public int GetGroupVariableValueByGroup(int var1, String var2, int var3){ + logger.debug("[LUA] Call GetGroupVariableValueByGroup with {},{},{}", + var1,var2,var3); + + //TODO + + return getSceneScriptManager().getVariables().getOrDefault(var2, 0); + } + + public int SetIsAllowUseSkill(int canUse, int var2){ + logger.debug("[LUA] Call SetIsAllowUseSkill with {},{}", + canUse,var2); + + getSceneScriptManager().getScene().broadcastPacket(new PacketCanUseSkillNotify(canUse == 1)); + return 0; + } + + public int KillEntityByConfigId(LuaTable table){ + logger.debug("[LUA] Call KillEntityByConfigId with {}", + table); + var configId = table.get("config_id"); + if(configId == LuaValue.NIL){ + return 1; + } + + var entity = getSceneScriptManager().getScene().getEntityByConfigId(configId.toint()); + if(entity == null){ + return 1; + } + getSceneScriptManager().getScene().killEntity(entity, 0); + return 0; + } + } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index a13db7b68..690cd3d0d 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -1,17 +1,21 @@ package emu.grasscutter.scripts.data; -import java.util.List; - import emu.grasscutter.utils.Position; +import java.util.List; +import java.util.Map; + public class SceneGroup { public transient int block_id; // Not an actual variable in the scripts but we will keep it here for reference public int id; public int refresh_id; public Position pos; - - public List monsters; + + /** + * ConfigId - Monster + */ + public Map monsters; public List gadgets; public List triggers; public List regions; diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketCanUseSkillNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketCanUseSkillNotify.java new file mode 100644 index 000000000..f8fe1314a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketCanUseSkillNotify.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.CanUseSkillNotifyOuterClass; + +public class PacketCanUseSkillNotify extends BasePacket { + + public PacketCanUseSkillNotify(boolean canUseSkill) { + super(PacketOpcodes.CanUseSkillNotify); + + CanUseSkillNotifyOuterClass.CanUseSkillNotify proto = CanUseSkillNotifyOuterClass.CanUseSkillNotify.newBuilder() + .setIsCanUseSkill(canUseSkill) + .build(); + + this.setData(proto); + } + +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 91d3f133c..1fc6831cb 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -19,4 +19,6 @@ + + \ No newline at end of file From 8e99cb4f35526b3cbc28bfe510869885e186bd56 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Sat, 7 May 2022 05:00:50 -0700 Subject: [PATCH 002/312] More reliable stamina calculation by separately handling immediate one-time cost and cost over time. --- .../MovementManager/MovementManager.java | 428 ++++++++---------- .../emu/grasscutter/game/player/Player.java | 2 +- .../grasscutter/game/player/TeamManager.java | 2 +- .../recv/HandlerCombatInvocationsNotify.java | 76 +++- .../recv/HandlerEvtDoSkillSuccNotify.java | 6 +- 5 files changed, 268 insertions(+), 246 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java b/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java index 23b45903a..ece02a0fb 100644 --- a/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java +++ b/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java @@ -7,22 +7,31 @@ import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.LifeState; import emu.grasscutter.game.props.PlayerProperty; -import emu.grasscutter.net.proto.EntityMoveInfoOuterClass; +import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo; +import emu.grasscutter.net.proto.EvtDoSkillSuccNotifyOuterClass.EvtDoSkillSuccNotify; import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; -import emu.grasscutter.net.proto.VectorOuterClass; +import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.Position; -import org.jetbrains.annotations.NotNull; import java.lang.Math; import java.util.*; public class MovementManager { - - public HashMap> MotionStatesCategorized = new HashMap<>(); + private final Player player; + private HashMap> MotionStatesCategorized = new HashMap<>(); + private Position currentCoordinates = new Position(0, 0, 0); + private Position previousCoordinates = new Position(0, 0, 0); + private MotionState currentState = MotionState.MOTION_STANDBY; + private MotionState previousState = MotionState.MOTION_STANDBY; + private Timer sustainedStaminaHandlerTimer; + private GameSession cachedSession = null; + private GameEntity cachedEntity = null; + private int staminaRecoverDelay = 0; + private boolean isInSkillMove = false; private enum ConsumptionType { None(0), @@ -31,8 +40,8 @@ public class MovementManager { CLIMB_START(-500), CLIMBING(-150), CLIMB_JUMP(-2500), - DASH(-1800), - SPRINT(-360), + SPRINT(-1800), + DASH(-360), FLY(-60), SWIM_DASH_START(-200), SWIM_DASH(-200), @@ -47,6 +56,7 @@ public class MovementManager { POWERED_FLY(500); public final int amount; + ConsumptionType(int amount) { this.amount = amount; } @@ -55,33 +65,26 @@ public class MovementManager { private class Consumption { public ConsumptionType consumptionType; public int amount; + public Consumption(ConsumptionType ct, int a) { consumptionType = ct; amount = a; } + public Consumption(ConsumptionType ct) { this(ct, ct.amount); } } - private MotionState previousState = MotionState.MOTION_STANDBY; - private MotionState currentState = MotionState.MOTION_STANDBY; - private Position previousCoordinates = new Position(0, 0, 0); - private Position currentCoordinates = new Position(0, 0, 0); + public boolean getIsInSkillMove() { + return isInSkillMove; + } - private final Player player; - - private float landSpeed = 0; - private long landTimeMillisecond = 0; - private Timer movementManagerTickTimer; - private GameSession cachedSession = null; - private GameEntity cachedEntity = null; - private int staminaRecoverDelay = 0; - private int skillCaster = 0; - private int skillCasting = 0; + public void setIsInSkillMove(boolean b) { + isInSkillMove = b; + } public MovementManager(Player player) { - previousCoordinates.add(new Position(0,0,0)); this.player = player; MotionStatesCategorized.put("SWIM", new HashSet<>(Arrays.asList( @@ -129,252 +132,225 @@ public class MovementManager { ))); MotionStatesCategorized.put("FIGHT", new HashSet<>(Arrays.asList( - MotionState.MOTION_FIGHT + MotionState.MOTION_FIGHT ))); - - - } - - public void handle(GameSession session, EntityMoveInfoOuterClass.EntityMoveInfo moveInfo, GameEntity entity) { - if (movementManagerTickTimer == null) { - movementManagerTickTimer = new Timer(); - movementManagerTickTimer.scheduleAtFixedRate(new MotionManagerTick(), 0, 200); - } - // cache info for later use in tick - cachedSession = session; - cachedEntity = entity; - - MotionInfo motionInfo = moveInfo.getMotionInfo(); - moveEntity(entity, moveInfo); - VectorOuterClass.Vector posVector = motionInfo.getPos(); - Position newPos = new Position(posVector.getX(), - posVector.getY(), posVector.getZ());; - if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) { - currentCoordinates = newPos; - } - currentState = motionInfo.getState(); - Grasscutter.getLogger().debug("" + currentState + "\t" + (moveInfo.getIsReliable() ? "reliable" : "")); - handleFallOnGround(motionInfo); - } - - public void resetTimer() { - Grasscutter.getLogger().debug("MovementManager ticker stopped"); - movementManagerTickTimer.cancel(); - movementManagerTickTimer = null; - } - - private void moveEntity(GameEntity entity, EntityMoveInfoOuterClass.EntityMoveInfo moveInfo) { - entity.getPosition().set(moveInfo.getMotionInfo().getPos()); - entity.getRotation().set(moveInfo.getMotionInfo().getRot()); - entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime()); - entity.setLastMoveReliableSeq(moveInfo.getReliableSeq()); - entity.setMotionState(moveInfo.getMotionInfo().getState()); } private boolean isPlayerMoving() { float diffX = currentCoordinates.getX() - previousCoordinates.getX(); float diffY = currentCoordinates.getY() - previousCoordinates.getY(); float diffZ = currentCoordinates.getZ() - previousCoordinates.getZ(); - // Grasscutter.getLogger().debug("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates + ", " + diffX + ", " + diffY + ", " + diffZ); - return Math.abs(diffX) > 0.2 || Math.abs(diffY) > 0.1 || Math.abs(diffZ) > 0.2; + Grasscutter.getLogger().debug("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates + + ", " + diffX + ", " + diffY + ", " + diffZ); + return Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.2 || Math.abs(diffZ) > 0.3; } - private int getCurrentStamina() { - return player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); - } - - private int getMaximumStamina() { - return player.getProperty(PlayerProperty.PROP_MAX_STAMINA); - } - - // Returns new stamina - public int updateStamina(GameSession session, int amount) { - int currentStamina = session.getPlayer().getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); - if (amount == 0) { + // Returns new stamina and sends PlayerPropNotify + public int updateStamina(GameSession session, Consumption consumption) { + int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + if (consumption.amount == 0) { return currentStamina; } - int playerMaxStamina = session.getPlayer().getProperty(PlayerProperty.PROP_MAX_STAMINA); - int newStamina = currentStamina + amount; + int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); + Grasscutter.getLogger().debug(currentStamina + "/" + playerMaxStamina + "\t" + currentState + "\t" + + (isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.consumptionType + "," + + consumption.amount + ")"); + int newStamina = currentStamina + consumption.amount; if (newStamina < 0) { newStamina = 0; } if (newStamina > playerMaxStamina) { newStamina = playerMaxStamina; } - session.getPlayer().setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); + player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); return newStamina; } - private void handleFallOnGround(@NotNull MotionInfo motionInfo) { - MotionState state = motionInfo.getState(); - // land speed and fall on ground event arrive in different packets - // cache land speed - if (state == MotionState.MOTION_LAND_SPEED) { - landSpeed = motionInfo.getSpeed().getY(); - landTimeMillisecond = System.currentTimeMillis(); + // Kills avatar, removes entity and sends notification. + // TODO: Probably move this to Avatar class? since other components may also need to kill avatar. + public void killAvatar(GameSession session, GameEntity entity, PlayerDieType dieType) { + session.send(new PacketAvatarLifeStateChangeNotify(player.getTeamManager().getCurrentAvatarEntity().getAvatar(), + LifeState.LIFE_DEAD, dieType)); + session.send(new PacketLifeStateChangeNotify(entity, LifeState.LIFE_DEAD, dieType)); + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0); + entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); + entity.getWorld().broadcastPacket(new PacketLifeStateChangeNotify(0, entity, LifeState.LIFE_DEAD)); + player.getScene().removeEntity(entity); + ((EntityAvatar) entity).onDeath(dieType, 0); + } + + public void startSustainedStaminaHandler() { + if (sustainedStaminaHandlerTimer == null) { + sustainedStaminaHandlerTimer = new Timer(); + sustainedStaminaHandlerTimer.scheduleAtFixedRate(new SustainedStaminaHandler(), 0, 200); + Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer started"); } - if (state == MotionState.MOTION_FALL_ON_GROUND) { - // if not received immediately after MOTION_LAND_SPEED, discard this packet. - // TODO: Test in high latency. - int maxDelay = 200; - if ((System.currentTimeMillis() - landTimeMillisecond) > maxDelay) { - Grasscutter.getLogger().debug("MOTION_FALL_ON_GROUND received after " + maxDelay + "ms, discard."); - return; + } + + public void stopSustainedStaminaHandler() { + Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer stopped"); + sustainedStaminaHandlerTimer.cancel(); + sustainedStaminaHandlerTimer = null; + } + + // Handlers + + // External trigger handler + + public void handleEvtDoSkillSuccNotify(GameSession session, EvtDoSkillSuccNotify notify) { + handleImmediateStamina(session, notify); + } + + public void handleCombatInvocationsNotify(GameSession session, EntityMoveInfo moveInfo, GameEntity entity) { + // cache info for later use in SustainedStaminaHandler tick + cachedSession = session; + cachedEntity = entity; + MotionInfo motionInfo = moveInfo.getMotionInfo(); + MotionState motionState = motionInfo.getState(); + boolean isReliable = moveInfo.getIsReliable(); + Grasscutter.getLogger().trace("" + motionState + "\t" + (isReliable ? "reliable" : "")); + if (isReliable) { + currentState = motionState; + Vector posVector = motionInfo.getPos(); + Position newPos = new Position(posVector.getX(), posVector.getY(), posVector.getZ()); + if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) { + currentCoordinates = newPos; } - float currentHP = cachedEntity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); - float maxHP = cachedEntity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); - float damage = 0; - Grasscutter.getLogger().debug("LandSpeed: " + landSpeed); - if (landSpeed < -23.5) { - damage = (float)(maxHP * 0.33); + } + startSustainedStaminaHandler(); + handleImmediateStamina(session, motionInfo, motionState, entity); + } + + // Internal handler + + private void handleImmediateStamina(GameSession session, MotionInfo motionInfo, MotionState motionState, + GameEntity entity) { + switch (motionState) { + case MOTION_DASH_BEFORE_SHAKE: + if (previousState != MotionState.MOTION_DASH_BEFORE_SHAKE) { + updateStamina(session, new Consumption(ConsumptionType.SPRINT)); + } + break; + case MOTION_CLIMB_JUMP: + if (previousState != MotionState.MOTION_CLIMB_JUMP) { + updateStamina(session, new Consumption(ConsumptionType.CLIMB_JUMP)); + } + break; + case MOTION_SWIM_DASH: + if (previousState != MotionState.MOTION_SWIM_DASH) { + updateStamina(session, new Consumption(ConsumptionType.SWIM_DASH_START)); + } + break; + } + } + + private void handleImmediateStamina(GameSession session, EvtDoSkillSuccNotify notify) { + Consumption consumption = getFightConsumption(notify.getSkillId()); + updateStamina(session, consumption); + } + + private class SustainedStaminaHandler extends TimerTask { + public void run() { + if (Grasscutter.getConfig().OpenStamina) { + boolean moving = isPlayerMoving(); + int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + int maxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); + if (moving || (currentStamina < maxStamina)) { + Grasscutter.getLogger().debug("Player moving: " + moving + ", stamina full: " + + (currentStamina >= maxStamina) + ", recalculate stamina"); + Consumption consumption = new Consumption(ConsumptionType.None); + if (!isInSkillMove) { + if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { + consumption = getClimbSustainedConsumption(); + } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { + consumption = getSwimSustainedConsumptions(); + } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { + consumption = getRunWalkDashSustainedConsumption(); + } else if (MotionStatesCategorized.get("FLY").contains(currentState)) { + consumption = getFlySustainedConsumption(); + } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { + consumption = getStandSustainedConsumption(); + } + } + if (cachedSession != null) { + if (consumption.amount < 0) { + staminaRecoverDelay = 0; + } + if (consumption.amount > 0 && consumption.consumptionType != ConsumptionType.POWERED_FLY) { + // For POWERED_FLY recover immediately - things like Amber's gliding exam may require this. + if (staminaRecoverDelay < 10) { + // For others recover after 2 seconds (10 ticks) - as official server does. + staminaRecoverDelay++; + consumption = new Consumption(ConsumptionType.None); + } + } + updateStamina(cachedSession, consumption); + } + handleDrowning(); + } } - if (landSpeed < -25) { - damage = (float)(maxHP * 0.5); - } - if (landSpeed < -26.5) { - damage = (float)(maxHP * 0.66); - } - if (landSpeed < -28) { - damage = (maxHP * 1); - } - float newHP = currentHP - damage; - if (newHP < 0) { - newHP = 0; - } - Grasscutter.getLogger().debug("Max: " + maxHP + "\tCurr: " + currentHP + "\tDamage: " + damage + "\tnewHP: " + newHP); - cachedEntity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); - cachedEntity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(cachedEntity, FightProperty.FIGHT_PROP_CUR_HP)); - if (newHP == 0) { - killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_FALL); - } - landSpeed = 0; + previousState = currentState; + previousCoordinates = new Position( + currentCoordinates.getX(), + currentCoordinates.getY(), + currentCoordinates.getZ() + ); } } private void handleDrowning() { - int stamina = getCurrentStamina(); + int stamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); if (stamina < 10) { boolean isSwimming = MotionStatesCategorized.get("SWIM").contains(currentState); - Grasscutter.getLogger().debug(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState + "\t" + isSwimming); + Grasscutter.getLogger().debug(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + + player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState + "\t" + isSwimming); if (isSwimming && currentState != MotionState.MOTION_SWIM_IDLE) { killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_DRAWN); } } } - public void killAvatar(GameSession session, GameEntity entity, PlayerDieType dieType) { - cachedSession.send(new PacketAvatarLifeStateChangeNotify( - cachedSession.getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), - LifeState.LIFE_DEAD, - dieType - )); - cachedSession.send(new PacketLifeStateChangeNotify( - cachedEntity, - LifeState.LIFE_DEAD, - dieType - )); - cachedEntity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0); - cachedEntity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(cachedEntity, FightProperty.FIGHT_PROP_CUR_HP)); - entity.getWorld().broadcastPacket(new PacketLifeStateChangeNotify(0, entity, LifeState.LIFE_DEAD)); - session.getPlayer().getScene().removeEntity(entity); - ((EntityAvatar)entity).onDeath(dieType, 0); - } + // Consumption Calculators - private class MotionManagerTick extends TimerTask - { - public void run() { - if (Grasscutter.getConfig().OpenStamina) { - boolean moving = isPlayerMoving(); - if (moving || (getCurrentStamina() < getMaximumStamina())) { - // Grasscutter.getLogger().debug("Player moving: " + moving + ", stamina full: " + (getCurrentStamina() >= getMaximumStamina()) + ", recalculate stamina"); - Consumption consumption = new Consumption(ConsumptionType.None); - - // TODO: refactor these conditions. - if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { - consumption = getClimbConsumption(); - } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { - consumption = getSwimConsumptions(); - } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { - consumption = getRunWalkDashConsumption(); - } else if (MotionStatesCategorized.get("FLY").contains(currentState)) { - consumption = getFlyConsumption(); - } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { - consumption = getStandConsumption(); - } else if (MotionStatesCategorized.get("FIGHT").contains(currentState)) { - consumption = getFightConsumption(); - } - - // delay 2 seconds before start recovering - as official server does. - if (cachedSession != null) { - if (consumption.amount < 0) { - staminaRecoverDelay = 0; - } - if (consumption.amount > 0 && consumption.consumptionType != ConsumptionType.POWERED_FLY) { - if (staminaRecoverDelay < 10) { - staminaRecoverDelay++; - consumption = new Consumption(ConsumptionType.None); - } - } - // Grasscutter.getLogger().debug(getCurrentStamina() + "/" + getMaximumStamina() + "\t" + currentState + "\t" + "isMoving: " + isPlayerMoving() + "\t(" + consumption.consumptionType + "," + consumption.amount + ")"); - updateStamina(cachedSession, consumption.amount); - } - - // tick triggered - handleDrowning(); - } - } - - previousState = currentState; - previousCoordinates = new Position(currentCoordinates.getX(), - currentCoordinates.getY(), currentCoordinates.getZ());; - } - } - - private Consumption getClimbConsumption() { + private Consumption getFightConsumption(int skillCasting) { Consumption consumption = new Consumption(ConsumptionType.None); - if (currentState == MotionState.MOTION_CLIMB) { + HashMap fightingCost = new HashMap<>() {{ + put(10013, -1000); // Kamisato Ayaka + put(10413, -1000); // Mona + }}; + if (fightingCost.containsKey(skillCasting)) { + consumption = new Consumption(ConsumptionType.FIGHT, fightingCost.get(skillCasting)); + } + return consumption; + } + + private Consumption getClimbSustainedConsumption() { + Consumption consumption = new Consumption(ConsumptionType.None); + if (currentState == MotionState.MOTION_CLIMB && isPlayerMoving()) { consumption = new Consumption(ConsumptionType.CLIMBING); if (previousState != MotionState.MOTION_CLIMB && previousState != MotionState.MOTION_CLIMB_JUMP) { consumption = new Consumption(ConsumptionType.CLIMB_START); } - if (!isPlayerMoving()) { - consumption = new Consumption(ConsumptionType.None); - } - } - if (currentState == MotionState.MOTION_CLIMB_JUMP) { - if (previousState != MotionState.MOTION_CLIMB_JUMP) { - consumption = new Consumption(ConsumptionType.CLIMB_JUMP); - } } return consumption; } - private Consumption getSwimConsumptions() { + private Consumption getSwimSustainedConsumptions() { Consumption consumption = new Consumption(ConsumptionType.None); if (currentState == MotionState.MOTION_SWIM_MOVE) { consumption = new Consumption(ConsumptionType.SWIMMING); } if (currentState == MotionState.MOTION_SWIM_DASH) { - consumption = new Consumption(ConsumptionType.SWIM_DASH_START); - if (previousState == MotionState.MOTION_SWIM_DASH) { - consumption = new Consumption(ConsumptionType.SWIM_DASH); - } + consumption = new Consumption(ConsumptionType.SWIM_DASH); } return consumption; } - private Consumption getRunWalkDashConsumption() { + private Consumption getRunWalkDashSustainedConsumption() { Consumption consumption = new Consumption(ConsumptionType.None); - if (currentState == MotionState.MOTION_DASH_BEFORE_SHAKE) { - consumption = new Consumption(ConsumptionType.DASH); - if (previousState == MotionState.MOTION_DASH_BEFORE_SHAKE) { - // only charge once - consumption = new Consumption(ConsumptionType.SPRINT); - } - } if (currentState == MotionState.MOTION_DASH) { - consumption = new Consumption(ConsumptionType.SPRINT); + consumption = new Consumption(ConsumptionType.DASH); } if (currentState == MotionState.MOTION_RUN) { consumption = new Consumption(ConsumptionType.RUN); @@ -385,22 +361,21 @@ public class MovementManager { return consumption; } - private Consumption getFlyConsumption() { + private Consumption getFlySustainedConsumption() { Consumption consumption = new Consumption(ConsumptionType.FLY); HashMap glidingCostReduction = new HashMap<>() {{ put(212301, 0.8f); // Amber put(222301, 0.8f); // Venti }}; float reduction = 1; - for (EntityAvatar entity: cachedSession.getPlayer().getTeamManager().getActiveTeam()) { - for (int skillId: entity.getAvatar().getProudSkillList()) { + for (EntityAvatar entity : cachedSession.getPlayer().getTeamManager().getActiveTeam()) { + for (int skillId : entity.getAvatar().getProudSkillList()) { if (glidingCostReduction.containsKey(skillId)) { reduction = glidingCostReduction.get(skillId); } } } consumption.amount *= reduction; - // POWERED_FLY, e.g. wind tunnel if (currentState == MotionState.MOTION_POWERED_FLY) { consumption = new Consumption(ConsumptionType.POWERED_FLY); @@ -408,7 +383,7 @@ public class MovementManager { return consumption; } - private Consumption getStandConsumption() { + private Consumption getStandSustainedConsumption() { Consumption consumption = new Consumption(ConsumptionType.None); if (currentState == MotionState.MOTION_STANDBY) { consumption = new Consumption(ConsumptionType.STANDBY); @@ -418,25 +393,4 @@ public class MovementManager { } return consumption; } - - private Consumption getFightConsumption() { - Consumption consumption = new Consumption(ConsumptionType.None); - HashMap fightingCost = new HashMap<>() {{ - put(10013, -1000); // Kamisato Ayaka - put(10413, -1000); // Mona - }}; - if (fightingCost.containsKey(skillCasting)) { - consumption = new Consumption(ConsumptionType.FIGHT, fightingCost.get(skillCasting)); - // only handle once, so reset. - skillCasting = 0; - skillCaster = 0; - } - return consumption; - } - - public void notifySkill(int caster, int skillId) { - skillCaster = caster; - skillCasting = skillId; - } } - diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 1eb5e3526..d2ce39d1c 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -1152,7 +1152,7 @@ public class Player { public void onLogout() { // stop stamina calculation - getMovementManager().resetTimer(); + getMovementManager().stopSustainedStaminaHandler(); // force to leave the dungeon if (getScene().getSceneType() == SceneType.SCENE_DUNGEON) { diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index 16e8942ad..775be2b87 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -557,7 +557,7 @@ public class TeamManager { // return; // } // } - player.getMovementManager().resetTimer(); // prevent drowning immediately after respawn + player.getMovementManager().stopSustainedStaminaHandler(); // prevent drowning immediately after respawn // Revive all team members for (EntityAvatar entity : getActiveTeam()) { diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java index 27e4ca6ff..c7bbccc5e 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java @@ -1,6 +1,8 @@ package emu.grasscutter.server.packet.recv; +import emu.grasscutter.Grasscutter; import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify; @@ -8,11 +10,21 @@ import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry; import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo; import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo; import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; +import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; +import emu.grasscutter.net.proto.PlayerDieTypeOuterClass; import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; + +import java.util.HashMap; @Opcodes(PacketOpcodes.CombatInvocationsNotify) public class HandlerCombatInvocationsNotify extends PacketHandler { + private float cachedLandingSpeed = 0; + private long cachedLandingTimeMillisecond = 0; + private boolean monitorLandingEvent = false; + @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { CombatInvocationsNotify notif = CombatInvocationsNotify.parseFrom(payload); @@ -28,7 +40,33 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { EntityMoveInfo moveInfo = EntityMoveInfo.parseFrom(entry.getCombatData()); GameEntity entity = session.getPlayer().getScene().getEntityById(moveInfo.getEntityId()); if (entity != null) { - session.getPlayer().getMovementManager().handle(session, moveInfo, entity); + // Move player + MotionInfo motionInfo = moveInfo.getMotionInfo(); + entity.getPosition().set(motionInfo.getPos()); + entity.getRotation().set(motionInfo.getRot()); + entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime()); + entity.setLastMoveReliableSeq(moveInfo.getReliableSeq()); + MotionState motionState = motionInfo.getState(); + entity.setMotionState(motionState); + + session.getPlayer().getMovementManager().handleCombatInvocationsNotify(session, moveInfo, entity); + + // TODO: handle MOTION_FIGHT landing + // For plunge attacks, LAND_SPEED is always -30 and is not useful. + // May need the height when starting plunge attack. + + if (monitorLandingEvent) { + if (motionState == MotionState.MOTION_FALL_ON_GROUND) { + monitorLandingEvent = false; + handleFallOnGround(session, entity, motionState); + } + } + if (motionState == MotionState.MOTION_LAND_SPEED) { + // MOTION_LAND_SPEED and MOTION_FALL_ON_GROUND arrive in different packet. Cache land speed for later use. + cachedLandingSpeed = motionInfo.getSpeed().getY(); + cachedLandingTimeMillisecond = System.currentTimeMillis(); + monitorLandingEvent = true; + } } break; default: @@ -47,5 +85,39 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { } } - + private void handleFallOnGround(GameSession session, GameEntity entity, MotionState motionState) { + // If not received immediately after MOTION_LAND_SPEED, discard this packet. + int maxDelay = 200; + long actualDelay = System.currentTimeMillis() - cachedLandingTimeMillisecond; + Grasscutter.getLogger().debug("MOTION_FALL_ON_GROUND received after " + actualDelay + "/" + maxDelay + "ms." + (actualDelay > maxDelay ? " Discard" : "")); + if (actualDelay > maxDelay) { + return; + } + float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + float damage = 0; + if (cachedLandingSpeed < -23.5) { + damage = (float) (maxHP * 0.33); + } + if (cachedLandingSpeed < -25) { + damage = (float) (maxHP * 0.5); + } + if (cachedLandingSpeed < -26.5) { + damage = (float) (maxHP * 0.66); + } + if (cachedLandingSpeed < -28) { + damage = (maxHP * 1); + } + float newHP = currentHP - damage; + if (newHP < 0) { + newHP = 0; + } + Grasscutter.getLogger().debug(currentHP + "/" + maxHP + "\t" + "\tDamage: " + damage + "\tnewHP: " + newHP); + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); + entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); + if (newHP == 0) { + session.getPlayer().getMovementManager().killAvatar(session, entity, PlayerDieTypeOuterClass.PlayerDieType.PLAYER_DIE_FALL); + } + cachedLandingSpeed = 0; + } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java index a57ae9665..6a08693bb 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java @@ -1,6 +1,5 @@ package emu.grasscutter.server.packet.recv; -import emu.grasscutter.Grasscutter; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketOpcodes; @@ -15,10 +14,7 @@ public class HandlerEvtDoSkillSuccNotify extends PacketHandler { EvtDoSkillSuccNotify notify = EvtDoSkillSuccNotify.parseFrom(payload); // TODO: Will be used for deducting stamina for charged skills. - int caster = notify.getCasterId(); - int skillId = notify.getSkillId(); - - session.getPlayer().getMovementManager().notifySkill(caster, skillId); + session.getPlayer().getMovementManager().handleEvtDoSkillSuccNotify(session, notify); } } From 836e0150b541251041a7575613a4bc45fb1d3195 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Sat, 7 May 2022 16:29:40 -0700 Subject: [PATCH 003/312] Stop stamina consumption on game pause --- .../{SotSManager => }/SotSManager.java | 7 +++-- ...vementManager.java => StaminaManager.java} | 19 ++++++++----- .../emu/grasscutter/game/player/Player.java | 27 +++++++++---------- .../grasscutter/game/player/TeamManager.java | 2 +- .../recv/HandlerCombatInvocationsNotify.java | 6 ++--- .../HandlerEnterTransPointRegionNotify.java | 11 +------- .../recv/HandlerEvtDoSkillSuccNotify.java | 2 +- .../HandlerExitTransPointRegionNotify.java | 2 +- 8 files changed, 33 insertions(+), 43 deletions(-) rename src/main/java/emu/grasscutter/game/managers/{SotSManager => }/SotSManager.java (95%) rename src/main/java/emu/grasscutter/game/managers/{MovementManager/MovementManager.java => StaminaManager.java} (94%) diff --git a/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java b/src/main/java/emu/grasscutter/game/managers/SotSManager.java similarity index 95% rename from src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java rename to src/main/java/emu/grasscutter/game/managers/SotSManager.java index 0bfdf9454..ed67c6a62 100644 --- a/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java +++ b/src/main/java/emu/grasscutter/game/managers/SotSManager.java @@ -1,14 +1,11 @@ -package emu.grasscutter.game.managers.SotSManager; +package emu.grasscutter.game.managers; import emu.grasscutter.Grasscutter; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.entity.EntityAvatar; -import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.game.managers.MovementManager.MovementManager; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.PlayerProperty; -import emu.grasscutter.game.world.World; import emu.grasscutter.net.proto.ChangeHpReasonOuterClass; import emu.grasscutter.net.proto.PropChangeReasonOuterClass; import emu.grasscutter.server.game.GameSession; @@ -29,6 +26,8 @@ public class SotSManager { private final Player player; private Timer autoRecoverTimer; + public final static int GlobalMaximumSpringVolume = 8500000; + public SotSManager(Player player) { this.player = player; } diff --git a/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager.java similarity index 94% rename from src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java rename to src/main/java/emu/grasscutter/game/managers/StaminaManager.java index ece02a0fb..5fd4f57b4 100644 --- a/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager.java @@ -1,4 +1,4 @@ -package emu.grasscutter.game.managers.MovementManager; +package emu.grasscutter.game.managers; import emu.grasscutter.Grasscutter; import emu.grasscutter.game.entity.EntityAvatar; @@ -20,9 +20,11 @@ import emu.grasscutter.utils.Position; import java.lang.Math; import java.util.*; -public class MovementManager { +public class StaminaManager { private final Player player; private HashMap> MotionStatesCategorized = new HashMap<>(); + + public final static int GlobalMaximumStamina = 24000; private Position currentCoordinates = new Position(0, 0, 0); private Position previousCoordinates = new Position(0, 0, 0); private MotionState currentState = MotionState.MOTION_STANDBY; @@ -84,7 +86,7 @@ public class MovementManager { isInSkillMove = b; } - public MovementManager(Player player) { + public StaminaManager(Player player) { this.player = player; MotionStatesCategorized.put("SWIM", new HashSet<>(Arrays.asList( @@ -181,11 +183,14 @@ public class MovementManager { } public void startSustainedStaminaHandler() { - if (sustainedStaminaHandlerTimer == null) { - sustainedStaminaHandlerTimer = new Timer(); - sustainedStaminaHandlerTimer.scheduleAtFixedRate(new SustainedStaminaHandler(), 0, 200); - Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer started"); + if (!player.isPaused()) { + if (sustainedStaminaHandlerTimer == null) { + sustainedStaminaHandlerTimer = new Timer(); + sustainedStaminaHandlerTimer.scheduleAtFixedRate(new SustainedStaminaHandler(), 0, 200); + Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer started"); + } } + } public void stopSustainedStaminaHandler() { diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index d2ce39d1c..3a2f3cbff 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -22,8 +22,8 @@ import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.mail.MailHandler; -import emu.grasscutter.game.managers.MovementManager.MovementManager; -import emu.grasscutter.game.managers.SotSManager.SotSManager; +import emu.grasscutter.game.managers.StaminaManager; +import emu.grasscutter.game.managers.SotSManager; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.PlayerProperty; @@ -62,9 +62,6 @@ import java.util.concurrent.LinkedBlockingQueue; @Entity(value = "players", useDiscriminator = false) public class Player { - @Transient private static int GlobalMaximumSpringVolume = 8500000; - @Transient private static int GlobalMaximumStamina = 24000; - @Id private int id; @Indexed(options = @IndexOptions(unique = true)) private String accountId; @@ -132,7 +129,7 @@ public class Player { @Transient private final InvokeHandler clientAbilityInitFinishHandler; private MapMarksManager mapMarksManager; - @Transient private MovementManager movementManager; + @Transient private StaminaManager staminaManager; private long springLastUsed; @@ -178,7 +175,7 @@ public class Player { this.expeditionInfo = new HashMap<>(); this.messageHandler = null; this.mapMarksManager = new MapMarksManager(); - this.movementManager = new MovementManager(this); + this.staminaManager = new StaminaManager(this); this.sotsManager = new SotSManager(this); } @@ -206,7 +203,7 @@ public class Player { this.getRotation().set(0, 307, 0); this.messageHandler = null; this.mapMarksManager = new MapMarksManager(); - this.movementManager = new MovementManager(this); + this.staminaManager = new StaminaManager(this); this.sotsManager = new SotSManager(this); } @@ -875,11 +872,11 @@ public class Player { } public void onPause() { - + staminaManager.stopSustainedStaminaHandler(); } public void onUnpause() { - + staminaManager.startSustainedStaminaHandler(); } public void sendPacket(BasePacket packet) { @@ -1024,7 +1021,7 @@ public class Player { return mapMarksManager; } - public MovementManager getMovementManager() { return movementManager; } + public StaminaManager getStaminaManager() { return staminaManager; } public SotSManager getSotSManager() { return sotsManager; } @@ -1152,7 +1149,7 @@ public class Player { public void onLogout() { // stop stamina calculation - getMovementManager().stopSustainedStaminaHandler(); + getStaminaManager().stopSustainedStaminaHandler(); // force to leave the dungeon if (getScene().getSceneType() == SceneType.SCENE_DUNGEON) { @@ -1214,7 +1211,7 @@ public class Player { } else if (prop == PlayerProperty.PROP_LAST_CHANGE_AVATAR_TIME) { // 10001 // TODO: implement sanity check } else if (prop == PlayerProperty.PROP_MAX_SPRING_VOLUME) { // 10002 - if (!(value >= 0 && value <= GlobalMaximumSpringVolume)) { return false; } + if (!(value >= 0 && value <= getSotSManager().GlobalMaximumSpringVolume)) { return false; } } else if (prop == PlayerProperty.PROP_CUR_SPRING_VOLUME) { // 10003 int playerMaximumSpringVolume = getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME); if (!(value >= 0 && value <= playerMaximumSpringVolume)) { return false; } @@ -1231,7 +1228,7 @@ public class Player { } else if (prop == PlayerProperty.PROP_IS_TRANSFERABLE) { // 10009 if (!(0 <= value && value <= 1)) { return false; } } else if (prop == PlayerProperty.PROP_MAX_STAMINA) { // 10010 - if (!(value >= 0 && value <= GlobalMaximumStamina)) { return false; } + if (!(value >= 0 && value <= getStaminaManager().GlobalMaximumStamina)) { return false; } } else if (prop == PlayerProperty.PROP_CUR_PERSIST_STAMINA) { // 10011 int playerMaximumStamina = getProperty(PlayerProperty.PROP_MAX_STAMINA); if (!(value >= 0 && value <= playerMaximumStamina)) { return false; } @@ -1242,7 +1239,7 @@ public class Player { } else if (prop == PlayerProperty.PROP_PLAYER_EXP) { // 10014 if (!(0 <= value)) { return false; } } else if (prop == PlayerProperty.PROP_PLAYER_HCOIN) { // 10015 - // see 10015 + // see PlayerProperty.PROP_PLAYER_HCOIN comments } else if (prop == PlayerProperty.PROP_PLAYER_SCOIN) { // 10016 // See 10015 } else if (prop == PlayerProperty.PROP_PLAYER_MP_SETTING_TYPE) { // 10017 diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index 775be2b87..204af2976 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -557,7 +557,7 @@ public class TeamManager { // return; // } // } - player.getMovementManager().stopSustainedStaminaHandler(); // prevent drowning immediately after respawn + player.getStaminaManager().stopSustainedStaminaHandler(); // prevent drowning immediately after respawn // Revive all team members for (EntityAvatar entity : getActiveTeam()) { diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java index c7bbccc5e..cc9e7b345 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java @@ -16,8 +16,6 @@ import emu.grasscutter.net.proto.PlayerDieTypeOuterClass; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; -import java.util.HashMap; - @Opcodes(PacketOpcodes.CombatInvocationsNotify) public class HandlerCombatInvocationsNotify extends PacketHandler { @@ -49,7 +47,7 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { MotionState motionState = motionInfo.getState(); entity.setMotionState(motionState); - session.getPlayer().getMovementManager().handleCombatInvocationsNotify(session, moveInfo, entity); + session.getPlayer().getStaminaManager().handleCombatInvocationsNotify(session, moveInfo, entity); // TODO: handle MOTION_FIGHT landing // For plunge attacks, LAND_SPEED is always -30 and is not useful. @@ -116,7 +114,7 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); if (newHP == 0) { - session.getPlayer().getMovementManager().killAvatar(session, entity, PlayerDieTypeOuterClass.PlayerDieType.PLAYER_DIE_FALL); + session.getPlayer().getStaminaManager().killAvatar(session, entity, PlayerDieTypeOuterClass.PlayerDieType.PLAYER_DIE_FALL); } cachedLandingSpeed = 0; } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java index 5591607fe..94c9bfd8b 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java @@ -1,20 +1,11 @@ package emu.grasscutter.server.packet.recv; -import emu.grasscutter.game.managers.SotSManager.SotSManager; +import emu.grasscutter.game.managers.SotSManager; import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason; -import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify; -import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify; -import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify; -import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; - -import java.util.List; @Opcodes(PacketOpcodes.EnterTransPointRegionNotify) public class HandlerEnterTransPointRegionNotify extends PacketHandler { diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java index 6a08693bb..705341fa0 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java @@ -14,7 +14,7 @@ public class HandlerEvtDoSkillSuccNotify extends PacketHandler { EvtDoSkillSuccNotify notify = EvtDoSkillSuccNotify.parseFrom(payload); // TODO: Will be used for deducting stamina for charged skills. - session.getPlayer().getMovementManager().handleEvtDoSkillSuccNotify(session, notify); + session.getPlayer().getStaminaManager().handleEvtDoSkillSuccNotify(session, notify); } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerExitTransPointRegionNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerExitTransPointRegionNotify.java index 35ec957cb..0d35c1762 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerExitTransPointRegionNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerExitTransPointRegionNotify.java @@ -1,6 +1,6 @@ package emu.grasscutter.server.packet.recv; -import emu.grasscutter.game.managers.SotSManager.SotSManager; +import emu.grasscutter.game.managers.SotSManager; import emu.grasscutter.game.player.Player; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketHandler; From 29f9d6506a2deb1e43a098027ec1c25acd8f7e8f Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Sat, 7 May 2022 18:07:44 -0700 Subject: [PATCH 004/312] Make stamina consumption classes public so others can use. --- .../managers/StaminaManager/Consumption.java | 15 +++++++ .../StaminaManager/ConsumptionType.java | 30 +++++++++++++ .../{ => StaminaManager}/StaminaManager.java | 42 +------------------ .../emu/grasscutter/game/player/Player.java | 2 +- 4 files changed, 47 insertions(+), 42 deletions(-) create mode 100644 src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java create mode 100644 src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java rename src/main/java/emu/grasscutter/game/managers/{ => StaminaManager}/StaminaManager.java (92%) diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java new file mode 100644 index 000000000..23eb44be9 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java @@ -0,0 +1,15 @@ +package emu.grasscutter.game.managers.StaminaManager; + +public class Consumption { + public ConsumptionType consumptionType; + public int amount; + + public Consumption(ConsumptionType ct, int a) { + consumptionType = ct; + amount = a; + } + + public Consumption(ConsumptionType ct) { + this(ct, ct.amount); + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java new file mode 100644 index 000000000..9a2d8ae24 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java @@ -0,0 +1,30 @@ +package emu.grasscutter.game.managers.StaminaManager; + +public enum ConsumptionType { + None(0), + + // consume + CLIMB_START(-500), + CLIMBING(-150), + CLIMB_JUMP(-2500), + SPRINT(-1800), + DASH(-360), + FLY(-60), + SWIM_DASH_START(-200), + SWIM_DASH(-200), + SWIMMING(-80), + FIGHT(0), + + // restore + STANDBY(500), + RUN(500), + WALK(500), + STANDBY_MOVE(500), + POWERED_FLY(500); + + public final int amount; + + ConsumptionType(int amount) { + this.amount = amount; + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java similarity index 92% rename from src/main/java/emu/grasscutter/game/managers/StaminaManager.java rename to src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index 5fd4f57b4..3d55e5edf 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -1,4 +1,4 @@ -package emu.grasscutter.game.managers; +package emu.grasscutter.game.managers.StaminaManager; import emu.grasscutter.Grasscutter; import emu.grasscutter.game.entity.EntityAvatar; @@ -35,48 +35,8 @@ public class StaminaManager { private int staminaRecoverDelay = 0; private boolean isInSkillMove = false; - private enum ConsumptionType { - None(0), - // consume - CLIMB_START(-500), - CLIMBING(-150), - CLIMB_JUMP(-2500), - SPRINT(-1800), - DASH(-360), - FLY(-60), - SWIM_DASH_START(-200), - SWIM_DASH(-200), - SWIMMING(-80), - FIGHT(0), - // restore - STANDBY(500), - RUN(500), - WALK(500), - STANDBY_MOVE(500), - POWERED_FLY(500); - - public final int amount; - - ConsumptionType(int amount) { - this.amount = amount; - } - } - - private class Consumption { - public ConsumptionType consumptionType; - public int amount; - - public Consumption(ConsumptionType ct, int a) { - consumptionType = ct; - amount = a; - } - - public Consumption(ConsumptionType ct) { - this(ct, ct.amount); - } - } public boolean getIsInSkillMove() { return isInSkillMove; diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 3a2f3cbff..b6956153c 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -22,7 +22,7 @@ import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.mail.MailHandler; -import emu.grasscutter.game.managers.StaminaManager; +import emu.grasscutter.game.managers.StaminaManager.StaminaManager; import emu.grasscutter.game.managers.SotSManager; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.EntityType; From eb94adb296a088e65e13c0e2e26dd8d184e55f5d Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Sat, 7 May 2022 21:47:13 +0800 Subject: [PATCH 005/312] Monsters tide turn by turn && Ban User Skill && Lua functions --- .../game/entity/EntityMonster.java | 1 + .../grasscutter/game/tower/TowerManager.java | 4 +- .../emu/grasscutter/game/world/Scene.java | 8 +- .../scripts/SceneScriptManager.java | 151 +++++++++++------- .../emu/grasscutter/scripts/ScriptLib.java | 123 +++++++++++--- .../grasscutter/scripts/data/SceneGroup.java | 12 +- .../packet/send/PacketCanUseSkillNotify.java | 19 +++ src/main/resources/logback.xml | 2 + 8 files changed, 232 insertions(+), 88 deletions(-) create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketCanUseSkillNotify.java diff --git a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java index c9d0c0982..0ae6f356b 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java @@ -117,6 +117,7 @@ public class EntityMonster extends GameEntity { this.getScene().getDeadSpawnedEntities().add(getSpawnEntry()); } if (getScene().getScriptManager().isInit() && this.getGroupId() > 0) { + getScene().getScriptManager().onMonsterDie(); getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, null); } if (getScene().getChallenge() != null && getScene().getChallenge().getGroup().id == this.getGroupId()) { diff --git a/src/main/java/emu/grasscutter/game/tower/TowerManager.java b/src/main/java/emu/grasscutter/game/tower/TowerManager.java index 51f840663..409549a1f 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerManager.java @@ -7,6 +7,7 @@ import emu.grasscutter.data.def.TowerLevelData; import emu.grasscutter.game.dungeons.DungeonSettleListener; import emu.grasscutter.game.dungeons.TowerDungeonSettleListener; import emu.grasscutter.game.player.Player; +import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; import emu.grasscutter.server.packet.send.PacketTowerCurLevelRecordChangeNotify; import emu.grasscutter.server.packet.send.PacketTowerEnterLevelRsp; @@ -75,7 +76,8 @@ public class TowerManager { player.getScene().setPrevScenePoint(enterPointId); player.getSession().send(new PacketTowerEnterLevelRsp(currentFloorId, currentLevel)); - + // stop using skill + player.getSession().send(new PacketCanUseSkillNotify(false)); } public void notifyCurLevelRecordChange(){ diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 97099c9b9..82ce9139f 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -105,7 +105,13 @@ public class Scene { public GameEntity getEntityById(int id) { return this.entities.get(id); } - + + public GameEntity getEntityByConfigId(int configId) { + return this.entities.values().stream() + .filter(x -> x.getConfigId() == configId) + .findFirst() + .orElse(null); + } /** * @return the autoCloseTime */ diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index 5f6a1b7e6..b8ba800a6 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -1,19 +1,14 @@ package emu.grasscutter.scripts; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import javax.script.Bindings; import javax.script.CompiledScript; import javax.script.ScriptException; -import org.luaj.vm2.LuaTable; import org.luaj.vm2.LuaValue; import org.luaj.vm2.lib.jse.CoerceJavaToLua; @@ -23,12 +18,8 @@ import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.data.def.WorldLevelData; import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.world.Scene; import emu.grasscutter.scripts.constants.EventType; -import emu.grasscutter.scripts.constants.ScriptGadgetState; -import emu.grasscutter.scripts.constants.ScriptRegionShape; import emu.grasscutter.scripts.data.SceneBlock; import emu.grasscutter.scripts.data.SceneConfig; import emu.grasscutter.scripts.data.SceneGadget; @@ -56,7 +47,12 @@ public class SceneScriptManager { private final Int2ObjectOpenHashMap> triggers; private final Int2ObjectOpenHashMap regions; - + private SceneGroup currentGroup; + private AtomicInteger monsterAlive; + private AtomicInteger monsterTideCount; + private int monsterSceneLimit; + private ConcurrentLinkedQueue monsterOrders; + public SceneScriptManager(Scene scene) { this.scene = scene; this.scriptLib = new ScriptLib(this); @@ -222,7 +218,8 @@ public class SceneScriptManager { cs.eval(getBindings()); // Set - group.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")); + group.monsters = ScriptLoader.getSerializer().toList(SceneMonster.class, bindings.get("monsters")).stream() + .collect(Collectors.toMap(x -> x.config_id, y -> y)); group.gadgets = ScriptLoader.getSerializer().toList(SceneGadget.class, bindings.get("gadgets")); group.triggers = ScriptLoader.getSerializer().toList(SceneTrigger.class, bindings.get("triggers")); group.suites = ScriptLoader.getSerializer().toList(SceneSuite.class, bindings.get("suites")); @@ -235,7 +232,7 @@ public class SceneScriptManager { // Add monsters to suite TODO optimize Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); - group.monsters.forEach(m -> map.put(m.config_id, m)); + group.monsters.entrySet().forEach(m -> map.put(m.getValue().config_id, m)); group.gadgets.forEach(m -> map.put(m.config_id, m)); for (SceneSuite suite : group.suites) { @@ -323,60 +320,92 @@ public class SceneScriptManager { } public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) { - spawnMonstersInGroup(group, group.getSuiteByIndex(suiteIndex)); + this.currentGroup = group; + this.monsterSceneLimit = 0; + var suite = group.getSuiteByIndex(suiteIndex); + if(suite == null){ + return; + } + suite.sceneMonsters.forEach(mob -> spawnMonstersInGroup(group, mob)); } public void spawnMonstersInGroup(SceneGroup group) { - spawnMonstersInGroup(group, null); + this.currentGroup = group; + this.monsterSceneLimit = 0; + group.monsters.values().forEach(mob -> spawnMonstersInGroup(group, mob)); } - - public void spawnMonstersInGroup(SceneGroup group, SceneSuite suite) { - List monsters = group.monsters; - - if (suite != null) { - monsters = suite.sceneMonsters; + public void spawnMonstersInGroup(SceneGroup group,Integer[] ordersConfigId, int tideCount, int sceneLimit) { + this.currentGroup = group; + this.monsterSceneLimit = sceneLimit; + this.monsterTideCount = new AtomicInteger(tideCount); + this.monsterAlive = new AtomicInteger(0); + this.monsterOrders = new ConcurrentLinkedQueue<>(List.of(ordersConfigId)); + + // add the last turn + group.monsters.keySet().stream() + .filter(i -> !this.monsterOrders.contains(i)) + .forEach(this.monsterOrders::add); + for (int i = 0; i < sceneLimit; i++) { + spawnMonstersInGroup(group, group.monsters.get(this.monsterOrders.poll())); + } + } + public void spawnMonstersInGroup(SceneGroup group, SceneMonster monster) { + if(monster == null){ + return; + } + if(this.monsterSceneLimit > 0){ + this.monsterTideCount.decrementAndGet(); + this.monsterAlive.incrementAndGet(); } - List toAdd = new ArrayList<>(); - - for (SceneMonster monster : monsters) { - MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); - - if (data == null) { - continue; - } - - // Calculate level - int level = monster.level; - - if (getScene().getDungeonData() != null) { - level = getScene().getDungeonData().getShowLevel(); - } else if (getScene().getWorld().getWorldLevel() > 0) { - WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel()); - - if (worldLevelData != null) { - level = worldLevelData.getMonsterLevel(); - } - } - - // Spawn mob - EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level); - entity.getRotation().set(monster.rot); - entity.setGroupId(group.id); - entity.setConfigId(monster.config_id); - - toAdd.add(entity); + MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); + + if (data == null) { + return; } - - if (toAdd.size() > 0) { - getScene().addEntities(toAdd); - - for (GameEntity entity : toAdd) { - callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId())); + + // Calculate level + int level = monster.level; + + if (getScene().getDungeonData() != null) { + level = getScene().getDungeonData().getShowLevel(); + } else if (getScene().getWorld().getWorldLevel() > 0) { + WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel()); + + if (worldLevelData != null) { + level = worldLevelData.getMonsterLevel(); + } + } + + // Spawn mob + EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level); + entity.getRotation().set(monster.rot); + entity.setGroupId(group.id); + entity.setConfigId(monster.config_id); + + getScene().addEntity(entity); + + callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId())); + } + + public void onMonsterDie(){ + if(this.monsterSceneLimit <= 0){ + return; + } + if(this.monsterAlive.decrementAndGet() >= this.monsterSceneLimit) { + // maybe not happen + return; + } + if(this.monsterTideCount.get() > 0){ + // add more + spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); + }else if(this.monsterAlive.get() == 0){ + // spawn the last turn of monsters + while(!this.monsterOrders.isEmpty()){ + spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); } } } - // Events public void callEvent(int eventType, ScriptArgs params) { @@ -405,4 +434,8 @@ public class SceneScriptManager { } } } + +// public LuaValue safetyCall(){ +// +// } } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index 941b00b60..1b9badc11 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -1,28 +1,24 @@ package emu.grasscutter.scripts; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.luaj.vm2.LuaTable; -import org.luaj.vm2.LuaValue; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.game.dungeons.DungeonChallenge; import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.data.SceneGroup; -import emu.grasscutter.scripts.data.SceneMonster; import emu.grasscutter.scripts.data.SceneRegion; -import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; import emu.grasscutter.server.packet.send.PacketGadgetStateNotify; import emu.grasscutter.server.packet.send.PacketWorktopOptionNotify; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Optional; public class ScriptLib { + public static final Logger logger = LoggerFactory.getLogger(ScriptLib.class); private final SceneScriptManager sceneScriptManager; public ScriptLib(SceneScriptManager sceneScriptManager) { @@ -34,6 +30,8 @@ public class ScriptLib { } public int SetGadgetStateByConfigId(int configId, int gadgetState) { + logger.debug("[LUA] Call SetGadgetStateByConfigId with {},{}", + configId,gadgetState); Optional entity = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getConfigId() == configId).findFirst(); @@ -53,6 +51,8 @@ public class ScriptLib { } public int SetGroupGadgetStateByConfigId(int groupId, int configId, int gadgetState) { + logger.debug("[LUA] Call SetGroupGadgetStateByConfigId with {},{},{}", + groupId,configId,gadgetState); List list = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getGroupId() == groupId).toList(); @@ -71,6 +71,8 @@ public class ScriptLib { } public int SetWorktopOptionsByGroupId(int groupId, int configId, int[] options) { + logger.debug("[LUA] Call SetWorktopOptionsByGroupId with {},{},{}", + groupId,configId,options); Optional entity = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst(); @@ -90,6 +92,8 @@ public class ScriptLib { } public int DelWorktopOptionByGroupId(int groupId, int configId, int option) { + logger.debug("[LUA] Call DelWorktopOptionByGroupId with {},{},{}",groupId,configId,option); + Optional entity = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst(); @@ -109,20 +113,24 @@ public class ScriptLib { } // Some fields are guessed - public int AutoMonsterTide(int challengeIndex, int groupId, int[] config_ids, int param4, int param5, int param6) { + public int AutoMonsterTide(int challengeIndex, int groupId, Integer[] ordersConfigId, int tideCount, int sceneLimit, int param6) { + logger.debug("[LUA] Call AutoMonsterTide with {},{},{},{},{},{}", + challengeIndex,groupId,ordersConfigId,tideCount,sceneLimit,param6); + SceneGroup group = getSceneScriptManager().getGroupById(groupId); if (group == null || group.monsters == null) { return 1; } - - // TODO just spawn all from group for now - this.getSceneScriptManager().spawnMonstersInGroup(group); + + this.getSceneScriptManager().spawnMonstersInGroup(group, ordersConfigId, tideCount, sceneLimit); return 0; } public int AddExtraGroupSuite(int groupId, int suite) { + logger.debug("[LUA] Call AddExtraGroupSuite with {},{}", + groupId,suite); SceneGroup group = getSceneScriptManager().getGroupById(groupId); if (group == null || group.monsters == null) { @@ -136,8 +144,17 @@ public class ScriptLib { } // param3 (probably time limit for timed dungeons) - public int ActiveChallenge(int challengeId, int challengeIndex, int param3, int groupId, int objectiveKills, int param5) { + public int ActiveChallenge(int challengeId, int challengeIndex, int timeLimitOrGroupId, int groupId, int objectiveKills, int param5) { + logger.debug("[LUA] Call ActiveChallenge with {},{},{},{},{},{}", + challengeId,challengeIndex,timeLimitOrGroupId,groupId,objectiveKills,param5); + SceneGroup group = getSceneScriptManager().getGroupById(groupId); + var objective = objectiveKills; + + if(group == null){ + group = getSceneScriptManager().getGroupById(timeLimitOrGroupId); + objective = groupId; + } if (group == null || group.monsters == null) { return 1; @@ -146,7 +163,7 @@ public class ScriptLib { DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(), group); challenge.setChallengeId(challengeId); challenge.setChallengeIndex(challengeIndex); - challenge.setObjective(objectiveKills); + challenge.setObjective(objective); getSceneScriptManager().getScene().setChallenge(challenge); @@ -155,26 +172,37 @@ public class ScriptLib { } public int GetGroupMonsterCountByGroupId(int groupId) { + logger.debug("[LUA] Call GetGroupMonsterCountByGroupId with {}", + groupId); return (int) getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e instanceof EntityMonster && e.getGroupId() == groupId) .count(); } public int GetGroupVariableValue(String var) { + logger.debug("[LUA] Call GetGroupVariableValue with {}", + var); return getSceneScriptManager().getVariables().getOrDefault(var, 0); } public int SetGroupVariableValue(String var, int value) { + logger.debug("[LUA] Call SetGroupVariableValue with {},{}", + var, value); getSceneScriptManager().getVariables().put(var, value); return 0; } public LuaValue ChangeGroupVariableValue(String var, int value) { + logger.debug("[LUA] Call ChangeGroupVariableValue with {},{}", + var, value); + getSceneScriptManager().getVariables().put(var, getSceneScriptManager().getVariables().get(var) + value); return LuaValue.ZERO; } public int RefreshGroup(LuaTable table) { + logger.debug("[LUA] Call RefreshGroup with {}", + table); // Kill and Respawn? int groupId = table.get("group_id").toint(); int suite = table.get("suite").toint(); @@ -192,6 +220,8 @@ public class ScriptLib { } public int GetRegionEntityCount(LuaTable table) { + logger.debug("[LUA] Call GetRegionEntityCount with {}", + table); int regionId = table.get("region_eid").toint(); int entityType = table.get("entity_type").toint(); @@ -205,21 +235,68 @@ public class ScriptLib { } public void PrintContextLog(String msg) { - Grasscutter.getLogger().info("[LUA] " + msg); + logger.info("[LUA] " + msg); } - public int TowerCountTimeStatus(int var1, int var2){ + public int TowerCountTimeStatus(int isDone, int var2){ + logger.debug("[LUA] Call TowerCountTimeStatus with {},{}", + isDone,var2); + // TODO record time return 0; } public int GetGroupMonsterCount(int var1){ - // Maybe... - return GetGroupMonsterCountByGroupId(var1); + logger.debug("[LUA] Call GetGroupMonsterCount with {}", + var1); + + return (int) getSceneScriptManager().getScene().getEntities().values().stream() + .filter(e -> e instanceof EntityMonster) + .count(); } public int SetMonsterBattleByGroup(int var1, int var2, int var3){ + logger.debug("[LUA] Call SetMonsterBattleByGroup with {},{},{}", + var1,var2,var3); + return 0; } public int CauseDungeonFail(int var1){ + logger.debug("[LUA] Call CauseDungeonFail with {}", + var1); + return 0; } + // 8-1 + public int GetGroupVariableValueByGroup(int var1, String var2, int var3){ + logger.debug("[LUA] Call GetGroupVariableValueByGroup with {},{},{}", + var1,var2,var3); + + //TODO + + return getSceneScriptManager().getVariables().getOrDefault(var2, 0); + } + + public int SetIsAllowUseSkill(int canUse, int var2){ + logger.debug("[LUA] Call SetIsAllowUseSkill with {},{}", + canUse,var2); + + getSceneScriptManager().getScene().broadcastPacket(new PacketCanUseSkillNotify(canUse == 1)); + return 0; + } + + public int KillEntityByConfigId(LuaTable table){ + logger.debug("[LUA] Call KillEntityByConfigId with {}", + table); + var configId = table.get("config_id"); + if(configId == LuaValue.NIL){ + return 1; + } + + var entity = getSceneScriptManager().getScene().getEntityByConfigId(configId.toint()); + if(entity == null){ + return 1; + } + getSceneScriptManager().getScene().killEntity(entity, 0); + return 0; + } + } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java index a13db7b68..690cd3d0d 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGroup.java @@ -1,17 +1,21 @@ package emu.grasscutter.scripts.data; -import java.util.List; - import emu.grasscutter.utils.Position; +import java.util.List; +import java.util.Map; + public class SceneGroup { public transient int block_id; // Not an actual variable in the scripts but we will keep it here for reference public int id; public int refresh_id; public Position pos; - - public List monsters; + + /** + * ConfigId - Monster + */ + public Map monsters; public List gadgets; public List triggers; public List regions; diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketCanUseSkillNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketCanUseSkillNotify.java new file mode 100644 index 000000000..f8fe1314a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketCanUseSkillNotify.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.CanUseSkillNotifyOuterClass; + +public class PacketCanUseSkillNotify extends BasePacket { + + public PacketCanUseSkillNotify(boolean canUseSkill) { + super(PacketOpcodes.CanUseSkillNotify); + + CanUseSkillNotifyOuterClass.CanUseSkillNotify proto = CanUseSkillNotifyOuterClass.CanUseSkillNotify.newBuilder() + .setIsCanUseSkill(canUseSkill) + .build(); + + this.setData(proto); + } + +} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 91d3f133c..1fc6831cb 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -19,4 +19,6 @@ + + \ No newline at end of file From 50307ea3a93e05ac1ae1e08ef01b3dd432a191b9 Mon Sep 17 00:00:00 2001 From: Zakhil Date: Sun, 8 May 2022 03:19:24 +0200 Subject: [PATCH 006/312] Added polish locale (#655) --- src/main/resources/languages/pl-PL.json | 298 ++++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 src/main/resources/languages/pl-PL.json diff --git a/src/main/resources/languages/pl-PL.json b/src/main/resources/languages/pl-PL.json new file mode 100644 index 000000000..e9ff74e25 --- /dev/null +++ b/src/main/resources/languages/pl-PL.json @@ -0,0 +1,298 @@ +{ + "messages": { + "game": { + "port_bind": "Serwer gry uruchomiony na porcie: %s", + "connect": "Klient połączył się z %s", + "disconnect": "Klient rozłączył się z %s", + "game_update_error": "Wystąpił błąd podczas aktualizacji gry.", + "command_error": "Błąd komendy:" + }, + "dispatch": { + "port_bind": "[Dispatch] Serwer dispatch wystartował na porcie %s", + "request": "[Dispatch] Klient %s %s zapytanie: %s", + "keystore": { + "general_error": "[Dispatch] Błąd łądowania keystore!", + "password_error": "[Dispatch] Nie można załadować keystore. Próba z domyślnym hasłem keystore...", + "no_keystore_error": "[Dispatch] Brak certyfikatu SSL! Przejście na serwer HTTP.", + "default_password": "[Dispatch] Domyślne hasło keystore zadziałało. Rozważ ustawienie go na 123456 w pliku config.json." + }, + "no_commands_error": "Komendy nie są wspierane w trybie DISPATCH_ONLY.", + "unhandled_request_error": "[Dispatch] Potencjalnie niepodtrzymane %s zapytanie: %s", + "account": { + "login_attempt": "[Dispatch] Klient %s próbuje się zalogować", + "login_success": "[Dispatch] Klient %s zalogował się jako %s", + "login_token_attempt": "[Dispatch] Klient %s próbuje się zalogować poprzez token", + "login_token_error": "[Dispatch] Klient %s nie mógł się zalogować poprzez token", + "login_token_success": "[Dispatch] Klient %s zalogował się poprzez token jako %s", + "combo_token_success": "[Dispatch] Klient %s pomyślnie wymienił combo token", + "combo_token_error": "[Dispatch] Klient %s nie wymienił combo token'u", + "account_login_create_success": "[Dispatch] Klient %s nie mógł się zalogować: Konto %s stworzone", + "account_login_create_error": "[Dispatch] Klient %s nie mógł się zalogować: Tworzenie konta nie powiodło się", + "account_login_exist_error": "[Dispatch] Klient %s nie mógł się zalogować: Nie znaleziono konta", + "account_cache_error": "Błąd pamięci cache konta gry", + "session_key_error": "Błędny klucz sesji.", + "username_error": "Nazwa użytkownika nie znaleziona.", + "username_create_error": "Nazwa użytkownika nie znaleziona, tworzenie nie powiodło się." + } + }, + "status": { + "free_software": "Grasscutter to DARMOWE oprogramowanie. Jeżeli ktoś Ci je sprzedał, to zostałeś oscamowany. Strona domowa: https://github.com/Grasscutters/Grasscutter", + "starting": "Uruchamianie Grasscutter...", + "shutdown": "Wyłączanie...", + "done": "Gotowe! Wpisz \"help\" aby uzyskać pomoc", + "error": "Wystąpił błąd.", + "welcome": "Witamy w Grasscutter", + "run_mode_error": "Błędny tryb pracy serwera: %s.", + "run_mode_help": "Tryb pracy serwera musi być ustawiony na 'HYBRID', 'DISPATCH_ONLY', lub 'GAME_ONLY'. Nie można wystartować Grasscutter...", + "create_resources": "Tworzenie folderu resources...", + "resources_error": "Umieść kopię 'BinOutput' i 'ExcelBinOutput' w folderze resources." + } + }, + "commands": { + "generic": { + "not_specified": "Nie podano komendy.", + "unknown_command": "Nieznana komenda: %s", + "permission_error": "Nie masz uprawnień do tej komendy.", + "console_execute_error": "Tą komende można wywołać tylko z konsoli.", + "player_execute_error": "Wywołaj tą komendę w grze.", + "command_exist_error": "Nie znaleziono komendy.", + "invalid": { + "amount": "Błędna ilość.", + "artifactId": "Błędne ID artefaktu.", + "avatarId": "Błędne id postaci.", + "avatarLevel": "Błędny poziom postaci.", + "entityId": "Błędne id obiektu.", + "id przedmiotu": "Błędne id przedmiotu.", + "itemLevel": "Błędny poziom przedmiotu.", + "itemRefinement": "Błędne ulepszenie.", + "playerId": "Błędne playerId.", + "uid": "Błędne UID." + } + }, + "execution": { + "uid_error": "Błędne UID.", + "player_exist_error": "Gracz nie znaleziony.", + "player_offline_error": "Gracz nie jest online.", + "item_id_error": "Błędne ID przedmiotu.", + "item_player_exist_error": "Błędny przedmiot lub UID.", + "entity_id_error": "Błędne ID obiektu.", + "player_exist_offline_error": "Gracz nie znaleziony lub jest offline.", + "argument_error": "Błędne argumenty.", + "clear_target": "Cel wyczyszczony.", + "set_target": "Następne komendy będą celować w @%s.", + "need_target": "Ta komenda wymaga docelowego UID. Dodaj argument <@UID> lub ustaw stały cel poleceniem /target @UID." + }, + "status": { + "enabled": "Włączone", + "disabled": "Wyłączone", + "help": "Pomoc", + "success": "Sukces" + }, + "account": { + "modify": "Modyfikuj konta użytkowników", + "invalid": "Błędne UID.", + "exists": "Konto już istnieje.", + "create": "Stworzono konto z UID %s.", + "delete": "Konto usunięte.", + "no_account": "Nie znaleziono konta.", + "command_usage": "Użycie: account [uid]" + }, + "broadcast": { + "command_usage": "Użycie: broadcast ", + "message_sent": "Wiadomość wysłana." + }, + "changescene": { + "usage": "Użycie: changescene ", + "already_in_scene": "Już jesteś na tej scenie.", + "success": "Zmieniono scene na %s.", + "exists_error": "Ta scena nie istenieje." + }, + "clear": { + "command_usage": "Użycie: clear ", + "weapons": "Wyczyszczono bronie dla %s.", + "artifacts": "Wyczyszczono artefakty dla %s.", + "materials": "Wyczyszczono materiały dla %s.", + "furniture": "Wyczyszczono meble dla %s.", + "displays": "Wyczyszczono displays dla %s.", + "virtuals": "Wyczyszczono virtuals dla %s.", + "everything": "Wyczyszczono wszystko dla %s." + }, + "coop": { + "usage": "Użycie: coop ", + "success": "Przyzwano %s do świata %s." + }, + "enter_dungeon": { + "usage": "Użycie: enterdungeon ", + "changed": "Zmieniono loch na %s", + "not_found_error": "Ten loch nie istnieje", + "in_dungeon_error": "Już jesteś w tym lochu" + }, + "giveAll": { + "usage": "Użycie: giveall [gracz] [ilość]", + "started": "Dodawanie wszystkich przedmiotów...", + "success": "Pomyślnie dodano wszystkie przedmioty dla %s.", + "invalid_amount_or_playerId": "Błędna ilość lub ID gracza." + }, + "giveArtifact": { + "usage": "Użycie: giveart|gart [gracz] [[,]]... [poziom]", + "id_error": "Błędne ID artefaktu.", + "success": "Dano %s dla %s." + }, + "giveChar": { + "usage": "Użycie: givechar [ilość]", + "given": "Dano %s z poziomem %s dla %s.", + "invalid_avatar_id": "Błędne ID postaci.", + "invalid_avatar_level": "Błędny poziom postaci.", + "invalid_avatar_or_player_id": "Błędne ID postaci lub gracza." + }, + "give": { + "usage": "Użycie: give [ilość] [poziom]", + "refinement_only_applicable_weapons": "Ulepszenie można zastosować tylko dla broni.", + "refinement_must_between_1_and_5": "Ulepszenie musi być pomiędzy 1, a 5.", + "given": "Dano %s %s dla %s.", + "given_with_level_and_refinement": "Dano %s z poziomem %s, ulepszeniem %s %s razy dla %s", + "given_level": "Dano %s z poziomem %s %s razy dla %s" + }, + "godmode": { + "success": "Godmode jest teraz %s dla %s." + }, + "heal": { + "success": "Wszystkie postacie zostały wyleczone." + }, + "kick": { + "player_kick_player": "Gracz [%s:%s] wyrzucił gracza [%s:%s]", + "server_kick_player": "Wyrzucono gracza [%s:%s]" + }, + "kill": { + "usage": "Użycie: killall [UID gracza] [ID sceny]", + "scene_not_found_in_player_world": "Scena nie znaleziona w świecie gracza", + "kill_monsters_in_scene": "Zabito %s potworów w scenie %s" + }, + "killCharacter": { + "usage": "Użycie: /killcharacter [ID gracza]", + "success": "Zabito aktualną postać gracza %s." + }, + "list": { + "success": "Teraz jest %s gracz(y) online:" + }, + "permission": { + "usage": "Użycie: permission ", + "add": "Dodano uprawnienie", + "has_error": "To konto już ma to uprawnienie!", + "remove": "Usunięto uprawnienie.", + "not_have_error": "To konto nie ma tych uprawnień!", + "account_error": "Konto nie może zostać znalezione." + }, + "position": { + "success": "Koordynaty: %.3f, %.3f, %.3f\nID sceny: %d" + }, + "reload": { + "reload_start": "Ponowne ładowanie konfiguracji.", + "reload_done": "Ponowne ładowanie zakończone." + }, + "resetConst": { + "reset_all": "Resetuj konstelacje wszystkich postaci.", + "success": "Konstelacje dla %s zostały zresetowane. Proszę zalogować się ponownie aby zobaczyć zmiany." + }, + "resetShopLimit": { + "usage": "Użycie: /resetshop " + }, + "sendMail": { + "usage": "Użycie: `/sendmail [id szablonu]`", + "user_not_exist": "Gracz o ID '%s' nie istnieje", + "start_composition": "Komponowanie wiadomości.\nProszę użyj `/sendmail ` aby kontynuować.\nMożesz użyć `/sendmail stop` w dowolnym momencie", + "templates": "Szablony zostaną zaimplementowane niedługo...", + "invalid_arguments": "Błędne argumenty.\nUżycie `/sendmail [id szablonu]`", + "send_cancel": "Anulowano wysyłanie wiadomości", + "send_done": "Wysłano wiadomość do gracza %s!", + "send_all_done": "Wysłano wiadomośc do wszystkich graczy!", + "not_composition_end": "Komponowanie nie jest na ostatnim etapie.\nProszę użyj `/sendmail %s` lub `/sendmail stop` aby anulować", + "please_use": "Proszę użyj `/sendmail %s`", + "set_title": "Tytuł wiadomości to teraz: '%s'.\nUżyj '/sendmail ' aby kontynuować.", + "set_contents": "Treść wiadomości to teraz '%s'.\nUżyj '/sendmail ' aby kontynuować.", + "set_message_sender": "Nadawca wiadomości to teraz '%s'.\nUżyj '/sendmail [ilość] [poziom]' aby kontynuować.", + "send": "Załączono %s %s (poziom %s) do wiadomości.\nDodaj więcej przedmiotów lub użyj `/sendmail finish` aby wysłać wiadomość.", + "invalid_arguments_please_use": "Błędne argumenty \nProszę użyj `/sendmail %s`", + "title": "", + "message": "", + "sender": "", + "arguments": " [ilość] [poziom]", + "error": "BŁĄD: niepoprawny etap konstrukcji: %s. Sprawdź konsolę aby dowiedzieć się więcej." + }, + "sendMessage": { + "usage": "Użycie: /sendmessage ", + "success": "Wiadomość wysłana." + }, + "setFetterLevel": { + "usage": "Użycie: setfetterlevel ", + "range_error": "Poziom przyjaźni musi być pomiędzy 0,a 10.", + "success": "Poziom przyjaźni ustawiono na: %s", + "level_error": "Błędny poziom przyjaźni." + }, + "setStats": { + "usage_console": "Użycie: setstats|stats @ ", + "usage_ingame": "Użycie: setstats|stats [@UID] ", + "help_message": "\n\tWartości dla Statystyka: hp | maxhp | def | atk | em | er | crate | cdmg | cdr | heal | heali | shield | defi\n\t(cont.) Bonus DMG żywiołu: epyro | ecryo | ehydro | egeo | edendro | eelectro | ephys\n\t(cont.) RES na żywioł: respyro | rescryo | reshydro | resgeo | resdendro | reselectro | resphys\n", + "value_error": "Błędna wartość statystyki.", + "uid_error": "Błędne UID.", + "player_error": "Gracza nie znaleziono lub jest offline.", + "set_self": "%s ustawiono na %s.", + "set_for_uid": "%s dla %s ustawiono na %s.", + "set_max_hp": "Maksymalne HP ustawione na %s." + }, + "setWorldLevel": { + "usage": "Użycie: setworldlevel ", + "value_error": "Poziom świata musi być pomiędzy 0, a 8", + "success": "Ustawiono poziom świata na: %s.", + "invalid_world_level": "Invalid world level." + }, + "spawn": { + "usage": "Użycie: /spawn [ilość] [poziom(tylko potwory)]", + "success": "Stworzono %s %s." + }, + "stop": { + "success": "Serwer wyłącza się..." + }, + "talent": { + "usage_1": "Aby ustawić poziom talentu: /talent set ", + "usage_2": "Inny sposób na ustawienie poziomu talentu: /talent ", + "usage_3": "Aby uzyskać ID talentu: /talent getid", + "lower_16": "Błędny poziom talentu. Poziom powinien być mniejszy niż 16", + "set_id": "Ustawiono talent na %s.", + "set_atk": "Ustawiono talent Atak Podstawowy na poziom %s.", + "set_e": "Ustawiono poziom talentu E na %s.", + "set_q": "Ustawiono poziom talentu Q na %s.", + "invalid_skill_id": "Błędne ID umiejętności.", + "set_this": "Ustawiono ten talent na poziom %s.", + "invalid_level": "Błędny poziom talentu.", + "normal_attack_id": "ID podstawowego ataku: %s.", + "e_skill_id": "ID umiejętności E: %s.", + "q_skill_id": "ID umiejętności Q: %s." + }, + "teleportAll": { + "success": "Przyzwano wszystkich graczy do Ciebie.", + "error": "Możesz użyć tej komendy wyłącznie w trybie MP." + }, + "teleport": { + "usage_server": "Użycie: /tp @ [ID sceny]", + "usage": "Użycie: /tp [@] [ID sceny]", + "specify_player_id": "Musisz określić ID gracza.", + "invalid_position": "Błędna pozycja.", + "success": "Przeteleportowano %s do %s, %s, %s w scenie %s" + }, + "weather": { + "usage": "Użycie: weather [ID klimatu]", + "success": "Zmieniono pogodę na %s z klimatem %s", + "invalid_id": "Błędne ID." + }, + "drop": { + "command_usage": "Użycie: drop [ilość]", + "success": "Wyrzucono %s of %s." + }, + "help": { + "usage": "Użycie: ", + "aliases": "Aliasy: ", + "available_commands": "Dostępne komendy: " + } + } +} \ No newline at end of file From 549471b76e9e84663317103b2e5e7aecdfd462a1 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Sat, 7 May 2022 23:44:35 -0400 Subject: [PATCH 007/312] Fix language fallback'ing --- src/main/java/emu/grasscutter/utils/Language.java | 6 ++++-- src/main/java/emu/grasscutter/utils/Utils.java | 8 +++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/emu/grasscutter/utils/Language.java b/src/main/java/emu/grasscutter/utils/Language.java index 0af77adc1..cda46e512 100644 --- a/src/main/java/emu/grasscutter/utils/Language.java +++ b/src/main/java/emu/grasscutter/utils/Language.java @@ -48,11 +48,13 @@ public final class Language { try { InputStream file = Grasscutter.class.getResourceAsStream("/languages/" + fileName); - if(file == null) { + String translationContents = Utils.readFromInputStream(file); + if(translationContents.equals("empty")) { file = Grasscutter.class.getResourceAsStream("/languages/" + fallback); + translationContents = Utils.readFromInputStream(file); } - languageData = Grasscutter.getGsonFactory().fromJson(Utils.readFromInputStream(file), JsonObject.class); + languageData = Grasscutter.getGsonFactory().fromJson(translationContents, JsonObject.class); } catch (Exception exception) { Grasscutter.getLogger().warn("Failed to load language file: " + fileName, exception); } diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index 6d11822f0..1d79c496e 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -18,6 +18,8 @@ import io.netty.buffer.Unpooled; import org.slf4j.Logger; +import javax.annotation.Nullable; + import static emu.grasscutter.utils.Language.translate; @SuppressWarnings({"UnusedReturnValue", "BooleanMethodIsAlwaysInverted"}) @@ -253,7 +255,9 @@ public final class Utils { * @param stream The input stream. * @return The string. */ - public static String readFromInputStream(InputStream stream) { + public static String readFromInputStream(@Nullable InputStream stream) { + if(stream == null) return "empty"; + StringBuilder stringBuilder = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { @@ -261,6 +265,8 @@ public final class Utils { } stream.close(); } catch (IOException e) { Grasscutter.getLogger().warn("Failed to read from input stream."); + } catch (NullPointerException ignored) { + return "empty"; } return stringBuilder.toString(); } From c9e8d6cad7f2a6fe8deeb77b25facb6b776d5626 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Sat, 7 May 2022 19:39:31 -0400 Subject: [PATCH 008/312] Add a plugin schema --- plugin-schema.json | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 plugin-schema.json diff --git a/plugin-schema.json b/plugin-schema.json new file mode 100644 index 000000000..4fc772416 --- /dev/null +++ b/plugin-schema.json @@ -0,0 +1,49 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "JSON schema for a Grasscutter Plugin", + "type": "object", + "additionalProperties": true, + "definitions": { + "plugin-name": { + "type": "string", + "pattern": "^[A-Za-z\\d_.-]+$" + } + }, + "required": [ "name", "description", "mainClass" ], + "properties": { + "name": { + "description": "The unique name of plugin.", + "$ref": "#/definitions/plugin-name" + }, + "mainClass": { + "description": "The plugin's initial class file.", + "type": "string", + "pattern": "^(?!org\\.bukkit\\.)([a-zA-Z_$][a-zA-Z\\d_$]*\\.)*[a-zA-Z_$][a-zA-Z\\d_$]*$" + }, + "version": { + "description": "A plugin revision identifier.", + "type": [ "string", "number" ] + }, + "description": { + "description": "Human readable plugin summary.", + "type": "string" + }, + "author": { + "description": "The plugin author.", + "type": "string" + }, + "authors": { + "description": "The plugin contributors.", + "type": "array", + "items": { + "type": "string" + } + }, + "website": { + "title": "Website", + "description": "The URL to the plugin's site", + "type": "string", + "format": "uri" + } + } +} \ No newline at end of file From 94a6a79b4a907fbf028ec03887aba4b4caceebbb Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Sat, 7 May 2022 21:07:06 -0700 Subject: [PATCH 009/312] Fix null reference on stamina timer when paused player disconnects. --- .../StaminaManager/StaminaManager.java | 30 ++++++++----------- .../emu/grasscutter/game/player/Player.java | 4 +-- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index 3d55e5edf..5065b12b3 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -29,19 +29,16 @@ public class StaminaManager { private Position previousCoordinates = new Position(0, 0, 0); private MotionState currentState = MotionState.MOTION_STANDBY; private MotionState previousState = MotionState.MOTION_STANDBY; - private Timer sustainedStaminaHandlerTimer; + private final Timer sustainedStaminaHandlerTimer = new Timer(); + private final SustainedStaminaHandler handleSustainedStamina = new SustainedStaminaHandler(); + private boolean timerRunning = false; private GameSession cachedSession = null; private GameEntity cachedEntity = null; private int staminaRecoverDelay = 0; private boolean isInSkillMove = false; - - - - public boolean getIsInSkillMove() { return isInSkillMove; } - public void setIsInSkillMove(boolean b) { isInSkillMove = b; } @@ -139,24 +136,23 @@ public class StaminaManager { entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); entity.getWorld().broadcastPacket(new PacketLifeStateChangeNotify(0, entity, LifeState.LIFE_DEAD)); player.getScene().removeEntity(entity); - ((EntityAvatar) entity).onDeath(dieType, 0); + ((EntityAvatar)entity).onDeath(dieType, 0); } public void startSustainedStaminaHandler() { - if (!player.isPaused()) { - if (sustainedStaminaHandlerTimer == null) { - sustainedStaminaHandlerTimer = new Timer(); - sustainedStaminaHandlerTimer.scheduleAtFixedRate(new SustainedStaminaHandler(), 0, 200); - Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer started"); - } + if (!player.isPaused() && !timerRunning) { + timerRunning = true; + sustainedStaminaHandlerTimer.scheduleAtFixedRate(handleSustainedStamina, 0, 200); + // Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer started"); } - } public void stopSustainedStaminaHandler() { - Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer stopped"); - sustainedStaminaHandlerTimer.cancel(); - sustainedStaminaHandlerTimer = null; + if (timerRunning) { + timerRunning = false; + sustainedStaminaHandlerTimer.cancel(); + // Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer stopped"); + } } // Handlers diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index b6956153c..71ae9d8c6 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -872,11 +872,11 @@ public class Player { } public void onPause() { - staminaManager.stopSustainedStaminaHandler(); + getStaminaManager().stopSustainedStaminaHandler(); } public void onUnpause() { - staminaManager.startSustainedStaminaHandler(); + getStaminaManager().startSustainedStaminaHandler(); } public void sendPacket(BasePacket packet) { From 06564da009c6a8cc40700209f41362e176aa22d2 Mon Sep 17 00:00:00 2001 From: Mateoust <46558043+Mateoust@users.noreply.github.com> Date: Sun, 8 May 2022 13:03:14 +0800 Subject: [PATCH 010/312] fix issues 646 --- src/main/resources/languages/zh-CN.json | 2 +- src/main/resources/languages/zh-TW.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index b9bc5b22a..4ce6196bc 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -173,7 +173,7 @@ "success": "已杀死 %s 目前使用的角色。" }, "list": { - "message": "目前在线人数:%s" + "success": "目前在线人数:%s" }, "permission": { "usage": "用法:permission ", diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index 49e0f256d..7a7f5c100 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -173,7 +173,7 @@ "success": "已殺死 %s 目前的場上角色。" }, "list": { - "message": "目前總線上人數:%s" + "success": "目前總線上人數:%s" }, "permission": { "usage": "用法:permission ", From 21cd002fbda1a62f4bb3daff8b500eb5420a52f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8A=8A=E6=9E=AB?= Date: Sun, 8 May 2022 10:45:16 +0800 Subject: [PATCH 011/312] Fix typo && update zh-CN.json --- src/main/resources/languages/zh-CN.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 4ce6196bc..e78b76776 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -36,10 +36,10 @@ } }, "status": { - "free_software": "Grasscutter 是免费开源软件。如果你是付费购买的,那已经被骗了。Github:https://github.com/Grasscutters/Grasscutter", + "free_software": "Grasscutter 是免费开源软件,遵循Apache-2.0 license。如果您是付费购买的,那您已经被骗了。项目地址:Github:https://github.com/Grasscutters/Grasscutter", "starting": "正在启动 Grasscutter...", "shutdown": "正在关闭...", - "done": "加載完成!输入 \"help\" 查看命令列表", + "done": "加载完成!输入 \"help\" 查看命令列表", "error": "发生了一个错误。", "welcome": "欢迎使用 Grasscutter", "run_mode_error": "无效的服务器运行模式: %s。", @@ -119,7 +119,7 @@ }, "coop": { "usage": "用法:coop ", - "success": "已召唤 %s 到 %s的世界" + "success": "已强制召唤 %s 到 %s的世界" }, "enter_dungeon": { "usage": "用法:enterdungeon ", @@ -188,7 +188,7 @@ }, "reload": { "reload_start": "正在重载配置文件和数据。", - "reload_done": "重装完毕。" + "reload_done": "重载完毕。" }, "resetConst": { "reset_all": "重置所有角色的命座。", @@ -260,14 +260,14 @@ "lower_16": "无效的天赋等级,天赋等级应低于16。", "set_id": "将天赋等级设为 %s。", "set_atk": "将普通攻击等级设为 %s。", - "set_e": "设定天赋E等级为 %s。", - "set_q": "设定天赋Q等级为 %s。", + "set_e": "设定元素战技等级为 %s。", + "set_q": "设定元素爆发等级为 %s。", "invalid_skill_id": "无效的技能ID。", "set_this": "将天赋等级设为 %s。", "invalid_level": "无效的天赋等级。", "normal_attack_id": "普通攻击的 ID 为 %s。", - "e_skill_id": "E技能ID %s。", - "q_skill_id": "Q技能ID %s。" + "e_skill_id": "元素战技ID %s。", + "q_skill_id": "元素爆发ID %s。" }, "teleportAll": { "success": "已将全部玩家传送到你的位置", @@ -287,7 +287,7 @@ }, "drop": { "command_usage": "用法:drop [amount]", - "success": "已將 %s x %s 丟在附近。" + "success": "已将 %s x %s 丟在附近。" }, "help": { "usage": "用法:", From d7a82e1c9ff8af7512afe6c61abbf3eac88e0310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8A=8A=E6=9E=AB?= Date: Sun, 8 May 2022 11:38:56 +0800 Subject: [PATCH 012/312] Update zh-CN.json --- src/main/resources/languages/zh-CN.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index e78b76776..18ff2165c 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -140,7 +140,7 @@ }, "giveChar": { "usage": "用法:givechar [amount]", - "given": "Given %s with level %s to %s.", + "given": "给予角色 %s 等级 %s 向UID %s.", "invalid_avatar_id": "无效的角色ID。", "invalid_avatar_level": "无效的角色等級。.", "invalid_avatar_or_player_id": "无效的角色ID/玩家ID。" @@ -151,7 +151,7 @@ "refinement_must_between_1_and_5": "精炼等级必须在 1 到 5 之间。", "given": "已将 %s 个 %s 给予 %s。", "given_with_level_and_refinement": "已将 %s [等級%s, 精炼%s] %s个给予 %s", - "given_level": "已将 %s 等级 %s %s 个给予 %s" + "given_level": "已将 %s 等级 %s %s 个给予UID %s" }, "godmode": { "success": "上帝模式被设置为 %s 。 [用户:%s]" From 4c911d6f55eb35e787b09de5e279cbb5371d4f66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8A=8A=E6=9E=AB?= Date: Sun, 8 May 2022 11:51:07 +0800 Subject: [PATCH 013/312] Update zh-CN.json --- src/main/resources/languages/zh-CN.json | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 18ff2165c..8faa0e4ae 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -41,7 +41,7 @@ "shutdown": "正在关闭...", "done": "加载完成!输入 \"help\" 查看命令列表", "error": "发生了一个错误。", - "welcome": "欢迎使用 Grasscutter", + "welcome": "欢迎使用 Grasscutter!珍惜这段美妙的旅途吧!", "run_mode_error": "无效的服务器运行模式: %s。", "run_mode_help": "服务器运行模式必须为 HYBRID、DISPATCH_ONLY 或 GAME_ONLY。Grasscutter 启动失败...", "create_resources": "正在创建 resources 目录...", @@ -52,12 +52,12 @@ "generic": { "not_specified": "没有指定命令。", "unknown_command": "未知的命令:%s", - "permission_error": "您没有执行此命令的权限。", - "console_execute_error": "此命令只能在服务器控制台执行。", - "player_execute_error": "此命令只能在游戏内执行。", - "command_exist_error": "找不到命令。", + "permission_error": "哼哼哼!您没有执行此命令的权限!请联系服务器管理员解决!", + "console_execute_error": "此命令只能在服务器控制台执行呐~", + "player_execute_error": "此命令只能在游戏内执行哦~", + "command_exist_error": "这条命令……好像找不到呢?。", "invalid": { - "amount": "无效的 数量.", + "amount": "无效的数量.", "artifactId": "无效的圣遗物ID。", "avatarId": "无效的角色ID。", "avatarLevel": "无效的角色等級。", @@ -147,14 +147,14 @@ }, "give": { "usage": "用法:give [amount] [level] [refinement]", - "refinement_only_applicable_weapons": "精炼等级参数仅在武器上可用", - "refinement_must_between_1_and_5": "精炼等级必须在 1 到 5 之间。", + "refinement_only_applicable_weapons": "精炼等阶参数仅在给予武器时可用", + "refinement_must_between_1_and_5": "精炼等阶必须在 1 到 5 之间。", "given": "已将 %s 个 %s 给予 %s。", "given_with_level_and_refinement": "已将 %s [等級%s, 精炼%s] %s个给予 %s", "given_level": "已将 %s 等级 %s %s 个给予UID %s" }, "godmode": { - "success": "上帝模式被设置为 %s 。 [用户:%s]" + "success": "上帝模式已被设置为 %s 。 [用户:%s]" }, "heal": { "success": "所有角色已被治疗。" @@ -204,7 +204,7 @@ "templates": "邮件模板尚未实装...", "invalid_arguments": "无效的参数。\n指令使用方法 `/sendmail [templateId]`", "send_cancel": "取消发送邮件", - "send_done": "已将邮件给 %s!", + "send_done": "已将邮件发送给 %s!", "send_all_done": "邮件已发送给所有人!", "not_composition_end": "现在邮件发送未到最后阶段。\n请使用 `/sendmail %s` 继续发送邮件,或使用 `/sendmail stop` 来停止发送邮件。", "please_use": "请使用 `/sendmail %s`", @@ -257,7 +257,7 @@ "usage_1": "设置天赋等级:/talent set ", "usage_2": "另一种设置天赋等级的命令使用方法:/talent ", "usage_3": "获取天赋ID指令用法:/talent getid", - "lower_16": "无效的天赋等级,天赋等级应低于16。", + "lower_16": "无效的天赋等级,天赋等级应小于等于15。", "set_id": "将天赋等级设为 %s。", "set_atk": "将普通攻击等级设为 %s。", "set_e": "设定元素战技等级为 %s。", @@ -271,7 +271,7 @@ }, "teleportAll": { "success": "已将全部玩家传送到你的位置", - "error": "命令仅限多人游戏使用。" + "error": "命令仅限处于多人游戏状态下使用。" }, "teleport": { "usage_server": "用法:/tp @ [scene id]", @@ -282,8 +282,8 @@ }, "weather": { "usage": "用法:weather [climateId]", - "success": "已将当前天气设定为 %s,气候则为 %s。", - "invalid_id": "无效的ID。" + "success": "已将当前天气设定为 %s,气候为 %s。", + "invalid_id": "无效的天气ID。" }, "drop": { "command_usage": "用法:drop [amount]", From 257a92f3ef354ef81d9550e53fd01aab4dc8cf0e Mon Sep 17 00:00:00 2001 From: Magix <27646710+KingRainbow44@users.noreply.github.com> Date: Sat, 7 May 2022 21:24:18 -0400 Subject: [PATCH 014/312] Update build.yml --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 49a9d8b53..d14ebf3e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,4 +31,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: Grasscutter - path: grasscutter-*-dev.jar + path: grasscutter-*.jar From a46d58e5f6417eb642f003b66566a5ee53c22642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=9D=97=E5=90=9B?= Date: Sun, 8 May 2022 10:33:53 +0800 Subject: [PATCH 015/312] Add command description multilingual --- .../java/emu/grasscutter/command/Command.java | 2 - .../grasscutter/command/CommandHandler.java | 3 + .../emu/grasscutter/command/CommandMap.java | 8 ++ .../command/commands/AccountCommand.java | 7 +- .../command/commands/BroadcastCommand.java | 8 +- .../command/commands/ChangeSceneCommand.java | 8 +- .../command/commands/ClearCommand.java | 6 +- .../command/commands/CoopCommand.java | 8 +- .../command/commands/DropCommand.java | 8 +- .../command/commands/EnterDungeonCommand.java | 8 +- .../command/commands/GiveAllCommand.java | 8 +- .../command/commands/GiveArtifactCommand.java | 7 +- .../command/commands/GiveCharCommand.java | 8 +- .../command/commands/GiveCommand.java | 10 +- .../command/commands/GodModeCommand.java | 9 +- .../command/commands/HealCommand.java | 8 +- .../command/commands/HelpCommand.java | 24 ++-- .../command/commands/KickCommand.java | 8 +- .../command/commands/KillAllCommand.java | 8 +- .../commands/KillCharacterCommand.java | 8 +- .../command/commands/ListCommand.java | 8 +- .../command/commands/PermissionCommand.java | 8 +- .../command/commands/PositionCommand.java | 8 +- .../command/commands/ReloadCommand.java | 8 +- .../command/commands/ResetConstCommand.java | 6 +- .../commands/ResetShopLimitCommand.java | 8 +- .../command/commands/RestartCommand.java | 9 +- .../command/commands/SendMailCommand.java | 10 +- .../command/commands/SendMessageCommand.java | 7 +- .../commands/SetFetterLevelCommand.java | 6 +- .../command/commands/SetStatsCommand.java | 8 +- .../commands/SetWorldLevelCommand.java | 6 +- .../command/commands/SpawnCommand.java | 8 +- .../command/commands/StopCommand.java | 8 +- .../command/commands/TalentCommand.java | 8 +- .../command/commands/TeleportAllCommand.java | 8 +- .../command/commands/TeleportCommand.java | 8 +- .../command/commands/WeatherCommand.java | 8 +- .../java/emu/grasscutter/tools/Tools.java | 12 +- src/main/resources/languages/en-US.json | 105 ++++++++++++------ 40 files changed, 306 insertions(+), 115 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/Command.java b/src/main/java/emu/grasscutter/command/Command.java index 734f454ea..c94804678 100644 --- a/src/main/java/emu/grasscutter/command/Command.java +++ b/src/main/java/emu/grasscutter/command/Command.java @@ -9,8 +9,6 @@ public @interface Command { String usage() default "No usage specified"; - String description() default "No description specified"; - String[] aliases() default {}; String permission() default ""; diff --git a/src/main/java/emu/grasscutter/command/CommandHandler.java b/src/main/java/emu/grasscutter/command/CommandHandler.java index ffe21c9be..dadb87bb3 100644 --- a/src/main/java/emu/grasscutter/command/CommandHandler.java +++ b/src/main/java/emu/grasscutter/command/CommandHandler.java @@ -6,6 +6,9 @@ import emu.grasscutter.game.player.Player; import java.util.List; public interface CommandHandler { + + String description(); + /** * Send a message to the target. * diff --git a/src/main/java/emu/grasscutter/command/CommandMap.java b/src/main/java/emu/grasscutter/command/CommandMap.java index a183c6ac3..6b6a852af 100644 --- a/src/main/java/emu/grasscutter/command/CommandMap.java +++ b/src/main/java/emu/grasscutter/command/CommandMap.java @@ -85,6 +85,14 @@ public final class CommandMap { return new LinkedHashMap<>(this.annotations); } + public HashMap getHandlersAndAnnotations() { + HashMap hashMap = new HashMap<>(); + this.commands.forEach((key, handler) -> { + hashMap.put(handler, this.annotations.get(key)); + }); + return hashMap; + } + /** * Returns a list of all registered commands. * diff --git a/src/main/java/emu/grasscutter/command/commands/AccountCommand.java b/src/main/java/emu/grasscutter/command/commands/AccountCommand.java index 627f4680f..1c5c8228e 100644 --- a/src/main/java/emu/grasscutter/command/commands/AccountCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/AccountCommand.java @@ -9,9 +9,14 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "account", usage = "account [uid]", description = "Modify user accounts") +@Command(label = "account", usage = "account [uid]") public final class AccountCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.account.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (sender != null) { diff --git a/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java b/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java index 1aa234919..fcabb131c 100644 --- a/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java @@ -9,10 +9,14 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "broadcast", usage = "broadcast ", - description = "Sends a message to all the players", aliases = {"b"}, permission = "server.broadcast") +@Command(label = "broadcast", usage = "broadcast ", aliases = {"b"}, permission = "server.broadcast") public final class BroadcastCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.broadcast.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (args.size() < 1) { diff --git a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java index 1a4e97927..ea7bfe0f6 100644 --- a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java @@ -8,9 +8,13 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "changescene", usage = "changescene ", - description = "Changes your scene", aliases = {"scene"}, permission = "player.changescene") +@Command(label = "changescene", usage = "changescene ", aliases = {"scene"}, permission = "player.changescene") public final class ChangeSceneCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.changescene.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/ClearCommand.java b/src/main/java/emu/grasscutter/command/commands/ClearCommand.java index 47d9f2c0d..d416bec52 100644 --- a/src/main/java/emu/grasscutter/command/commands/ClearCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ClearCommand.java @@ -13,11 +13,15 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; @Command(label = "clear", usage = "clear ", //Merged /clearartifacts and /clearweapons to /clear [uid] - description = "Deletes unequipped unlocked items, including yellow rarity ones from your inventory", aliases = {"clear"}, permission = "player.clearinv") public final class ClearCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.clear.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/CoopCommand.java b/src/main/java/emu/grasscutter/command/commands/CoopCommand.java index 96411019b..120665b76 100644 --- a/src/main/java/emu/grasscutter/command/commands/CoopCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/CoopCommand.java @@ -9,9 +9,13 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "coop", usage = "coop [host UID]", - description = "Forces someone to join the world of others", permission = "server.coop") +@Command(label = "coop", usage = "coop [host UID]", permission = "server.coop") public final class CoopCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.coop.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/DropCommand.java b/src/main/java/emu/grasscutter/command/commands/DropCommand.java index a33a32603..a187cfda8 100644 --- a/src/main/java/emu/grasscutter/command/commands/DropCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/DropCommand.java @@ -13,10 +13,14 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "drop", usage = "drop [amount]", - description = "Drops an item near you", aliases = {"d", "dropitem"}, permission = "server.drop") +@Command(label = "drop", usage = "drop [amount]", aliases = {"d", "dropitem"}, permission = "server.drop") public final class DropCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.drop.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java b/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java index 434e80c8f..d92d537e9 100644 --- a/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java @@ -8,9 +8,13 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "enterdungeon", usage = "enterdungeon ", - description = "Enter a dungeon", aliases = {"dungeon"}, permission = "player.enterdungeon") +@Command(label = "enterdungeon", usage = "enterdungeon ", aliases = {"dungeon"}, permission = "player.enterdungeon") public final class EnterDungeonCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.enter_dungeon.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java index bb11de3c2..5009a4462 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java @@ -15,10 +15,14 @@ import java.util.*; import static emu.grasscutter.utils.Language.translate; -@Command(label = "giveall", usage = "giveall [amount]", - description = "Gives all items", aliases = {"givea"}, permission = "player.giveall", threading = true) +@Command(label = "giveall", usage = "giveall [amount]", aliases = {"givea"}, permission = "player.giveall", threading = true) public final class GiveAllCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.giveAll.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java index 541cc440e..d41e83671 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java @@ -16,8 +16,13 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "giveart", usage = "giveart [[,]]... [level]", description = "Gives the player a specified artifact", aliases = {"gart"}, permission = "player.giveart") +@Command(label = "giveart", usage = "giveart [[,]]... [level]", aliases = {"gart"}, permission = "player.giveart") public final class GiveArtifactCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.giveArtifact.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java index 4b3279202..0e784639d 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java @@ -12,10 +12,14 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "givechar", usage = "givechar [level]", - description = "Gives the player a specified character", aliases = {"givec"}, permission = "player.givechar") +@Command(label = "givechar", usage = "givechar [level]", aliases = {"givec"}, permission = "player.givechar") public final class GiveCharCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.giveChar.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java index 2f020f7b3..3daa2dff3 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java @@ -1,6 +1,5 @@ package emu.grasscutter.command.commands; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.data.GameData; @@ -12,12 +11,12 @@ import emu.grasscutter.game.props.ActionReason; import java.util.LinkedList; import java.util.List; -import java.util.regex.Pattern; import java.util.regex.Matcher; +import java.util.regex.Pattern; import static emu.grasscutter.utils.Language.translate; -@Command(label = "give", usage = "give [amount] [level]", description = "Gives an item to you or the specified player", aliases = { +@Command(label = "give", usage = "give [amount] [level]", aliases = { "g", "item", "giveitem"}, permission = "player.give") public final class GiveCommand implements CommandHandler { Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java is a joke of a proglang that doesn't have raw string literals @@ -32,6 +31,11 @@ public final class GiveCommand implements CommandHandler { return -1; } + @Override + public String description() { + return translate("commands.give.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java b/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java index 9abebb8db..4fd2999a6 100644 --- a/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java @@ -1,6 +1,5 @@ package emu.grasscutter.command.commands; -import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.game.player.Player; @@ -9,10 +8,14 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "godmode", usage = "godmode [on|off|toggle]", - description = "Prevents you from taking damage. Defaults to toggle.", permission = "player.godmode") +@Command(label = "godmode", usage = "godmode [on|off|toggle]", permission = "player.godmode") public final class GodModeCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.godmode.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/HealCommand.java b/src/main/java/emu/grasscutter/command/commands/HealCommand.java index bb0b861b0..b459ecb8c 100644 --- a/src/main/java/emu/grasscutter/command/commands/HealCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/HealCommand.java @@ -11,9 +11,13 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "heal", usage = "heal|h", aliases = {"h"}, - description = "Heal all characters in your current team.", permission = "player.heal") +@Command(label = "heal", usage = "heal|h", aliases = {"h"}, permission = "player.heal") public final class HealCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.heal.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/HelpCommand.java b/src/main/java/emu/grasscutter/command/commands/HelpCommand.java index 93ac831b3..dbb85bf9d 100644 --- a/src/main/java/emu/grasscutter/command/commands/HelpCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/HelpCommand.java @@ -10,22 +10,26 @@ import java.util.*; import static emu.grasscutter.utils.Language.translate; -@Command(label = "help", usage = "help [command]", - description = "Sends the help message or shows information about a specified command") +@Command(label = "help", usage = "help [command]") public final class HelpCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.help.description"); + } + @Override public void execute(Player player, Player targetPlayer, List args) { if (args.size() < 1) { HashMap handlers = CommandMap.getInstance().getHandlers(); - List annotations = new ArrayList<>(); + HashMap annotations = new HashMap<>(); for (String key : handlers.keySet()) { Command annotation = handlers.get(key).getClass().getAnnotation(Command.class); if (!Arrays.asList(annotation.aliases()).contains(key)) { if (player != null && !Objects.equals(annotation.permission(), "") && !player.getAccount().hasPermission(annotation.permission())) continue; - annotations.add(annotation); + annotations.put(annotation, handlers.get(key)); } } @@ -39,7 +43,7 @@ public final class HelpCommand implements CommandHandler { } else { Command annotation = handler.getClass().getAnnotation(Command.class); - builder.append(" ").append(annotation.description()).append("\n"); + builder.append(" ").append(handler.description()).append("\n"); builder.append(translate("commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { builder.append("\n").append(translate("commands.help.aliases")); @@ -56,12 +60,12 @@ public final class HelpCommand implements CommandHandler { } } - void SendAllHelpMessage(Player player, List annotations) { + void SendAllHelpMessage(Player player, HashMap annotations) { if (player == null) { StringBuilder builder = new StringBuilder("\n" + translate("commands.help.available_commands") + "\n"); - annotations.forEach(annotation -> { + annotations.forEach((annotation, handler) -> { builder.append(annotation.label()).append("\n"); - builder.append(" ").append(annotation.description()).append("\n"); + builder.append(" ").append(handler.description()).append("\n"); builder.append(translate("commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { builder.append("\n").append(translate("commands.help.aliases")); @@ -76,9 +80,9 @@ public final class HelpCommand implements CommandHandler { CommandHandler.sendMessage(null, builder.toString()); } else { CommandHandler.sendMessage(player, translate("commands.help.available_commands")); - annotations.forEach(annotation -> { + annotations.forEach((annotation, handler) -> { StringBuilder builder = new StringBuilder(annotation.label()).append("\n"); - builder.append(" ").append(annotation.description()).append("\n"); + builder.append(" ").append(handler.description()).append("\n"); builder.append(translate("commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { builder.append("\n").append(translate("commands.help.aliases")); diff --git a/src/main/java/emu/grasscutter/command/commands/KickCommand.java b/src/main/java/emu/grasscutter/command/commands/KickCommand.java index 270e28150..71b487cc4 100644 --- a/src/main/java/emu/grasscutter/command/commands/KickCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KickCommand.java @@ -8,10 +8,14 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "kick", usage = "kick", - description = "Kicks the specified player from the server (WIP)", permission = "server.kick") +@Command(label = "kick", usage = "kick", permission = "server.kick") public final class KickCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.kick.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java b/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java index 423c60bbd..1fa51eec4 100644 --- a/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java @@ -12,10 +12,14 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "killall", usage = "killall [sceneId]", - description = "Kill all entities", permission = "server.killall") +@Command(label = "killall", usage = "killall [sceneId]", permission = "server.killall") public final class KillAllCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.kill.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java b/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java index f1e0f0f8c..f3fdb4998 100644 --- a/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java @@ -13,10 +13,14 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"}, - description = "Kills the players current character", permission = "player.killcharacter") +@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"}, permission = "player.killcharacter") public final class KillCharacterCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.killCharacter.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/ListCommand.java b/src/main/java/emu/grasscutter/command/commands/ListCommand.java index bc35e65e1..7834d2467 100644 --- a/src/main/java/emu/grasscutter/command/commands/ListCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ListCommand.java @@ -10,10 +10,14 @@ import java.util.Map; import static emu.grasscutter.utils.Language.translate; -@Command(label = "list", usage = "list [uid]", - description = "List online players", aliases = {"players"}) +@Command(label = "list", usage = "list [uid]", aliases = {"players"}) public final class ListCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.list.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { Map playersMap = Grasscutter.getGameServer().getPlayers(); diff --git a/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java b/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java index 69c8ce899..309451945 100644 --- a/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java @@ -10,10 +10,14 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "permission", usage = "permission ", - description = "Grants or removes a permission for a user", permission = "*") +@Command(label = "permission", usage = "permission ", permission = "*") public final class PermissionCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.permission.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/PositionCommand.java b/src/main/java/emu/grasscutter/command/commands/PositionCommand.java index 7f6548c5b..3a3b40a3f 100644 --- a/src/main/java/emu/grasscutter/command/commands/PositionCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/PositionCommand.java @@ -9,10 +9,14 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "position", usage = "position", aliases = {"pos"}, - description = "Get coordinates.") +@Command(label = "position", usage = "position", aliases = {"pos"}) public final class PositionCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.position.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java index 6c85d2024..8e3e5e5aa 100644 --- a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java @@ -9,10 +9,14 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "reload", usage = "reload", - description = "Reload server config", permission = "server.reload") +@Command(label = "reload", usage = "reload", permission = "server.reload") public final class ReloadCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.reload.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { CommandHandler.sendMessage(sender, translate("commands.reload.reload_start")); diff --git a/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java b/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java index 706fb95e0..9eba8e4c4 100644 --- a/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java @@ -11,10 +11,14 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; @Command(label = "resetconst", usage = "resetconst [all]", - description = "Resets the constellation level on your current active character, will need to relog after using the command to see any changes.", aliases = {"resetconstellation"}, permission = "player.resetconstellation") public final class ResetConstCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.resetConst.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java index aeae0abbf..bba8da32c 100644 --- a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java @@ -9,9 +9,13 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "resetshop", usage = "resetshop", - description = "Reset target player's shop refresh time.", permission = "server.resetshop") +@Command(label = "resetshop", usage = "resetshop", permission = "server.resetshop") public final class ResetShopLimitCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.status.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/RestartCommand.java b/src/main/java/emu/grasscutter/command/commands/RestartCommand.java index e3b8b2747..2c56ae443 100644 --- a/src/main/java/emu/grasscutter/command/commands/RestartCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/RestartCommand.java @@ -6,9 +6,16 @@ import emu.grasscutter.game.player.Player; import java.util.List; -@Command(label = "restart", usage = "restart - Restarts the current session") +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "restart", usage = "restart") public final class RestartCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.restart.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (sender == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java b/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java index 838bea567..a56d68165 100644 --- a/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java @@ -13,8 +13,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; @SuppressWarnings("ConstantConditions") -@Command(label = "sendmail", usage = "sendmail [templateId]", - description = "Sends mail to the specified user. The usage of this command changes based on it's composition state.", permission = "server.sendmail") +@Command(label = "sendmail", usage = "sendmail [templateId]", permission = "server.sendmail") public final class SendMailCommand implements CommandHandler { // TODO: You should be able to do /sendmail and then just send subsequent messages until you finish @@ -24,6 +23,11 @@ public final class SendMailCommand implements CommandHandler { // Key = User that is constructing the mail. private static final HashMap mailBeingConstructed = new HashMap(); + @Override + public String description() { + return translate("commands.sendMail.description"); + } + // Yes this is awful and I hate it. @Override public void execute(Player sender, Player targetPlayer, List args) { @@ -40,7 +44,7 @@ public final class SendMailCommand implements CommandHandler { MailBuilder mailBuilder; switch (args.get(0).toLowerCase()) { case "help" -> { - CommandHandler.sendMessage(sender, this.getClass().getAnnotation(Command.class).description() + "\nUsage: " + this.getClass().getAnnotation(Command.class).usage()); + CommandHandler.sendMessage(sender, this.description() + "\nUsage: " + this.getClass().getAnnotation(Command.class).usage()); return; } case "all" -> mailBuilder = new MailBuilder(true, new Mail()); diff --git a/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java b/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java index acf63dea0..36e53de10 100644 --- a/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java @@ -8,10 +8,15 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "say", usage = "say ", description = "Sends a message to a player as the server", +@Command(label = "say", usage = "say ", aliases = {"sendservmsg", "sendservermessage", "sendmessage"}, permission = "server.sendmessage") public final class SendMessageCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.sendMessage.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java b/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java index 7184c679c..e098d99a5 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java @@ -12,10 +12,14 @@ import emu.grasscutter.server.packet.send.PacketAvatarFetterDataNotify; import static emu.grasscutter.utils.Language.translate; @Command(label = "setfetterlevel", usage = "setfetterlevel ", - description = "Sets your fetter level for your current active character", aliases = {"setfetterlvl", "setfriendship"}, permission = "player.setfetterlevel") public final class SetFetterLevelCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.setFetterLevel.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java index 233eb4d73..11cb8ab80 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java @@ -15,8 +15,7 @@ import emu.grasscutter.utils.Language; import static emu.grasscutter.utils.Language.translate; -@Command(label = "setstats", usage = "setstats|stats ", - description = "Set fight property for your current active character", aliases = {"stats"}, permission = "player.setstats") +@Command(label = "setstats", usage = "setstats|stats ", aliases = {"stats"}, permission = "player.setstats") public final class SetStatsCommand implements CommandHandler { static class Stat { String name; @@ -174,6 +173,11 @@ public final class SetStatsCommand implements CommandHandler { stats.put("_nonextra_physical_add_hurt", new Stat("NONEXTRA_PHYSICAL_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_PHYSICAL_ADD_HURT, true)); } + @Override + public String description() { + return translate("commands.setStats.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { String syntax = sender == null ? translate("commands.setStats.usage_console") : translate("commands.setStats.ingame"); diff --git a/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java b/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java index 914d8cecc..16b10bc6c 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java @@ -10,10 +10,14 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; @Command(label = "setworldlevel", usage = "setworldlevel ", - description = "Sets your world level (Relog to see proper effects)", aliases = {"setworldlvl"}, permission = "player.setworldlevel") public final class SetWorldLevelCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.setWorldLevel.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java b/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java index c66a45b50..8a995402b 100644 --- a/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java @@ -22,10 +22,14 @@ import java.util.Random; import static emu.grasscutter.utils.Language.translate; -@Command(label = "spawn", usage = "spawn [amount] [level(monster only)]", - description = "Spawns an entity near you", permission = "server.spawn") +@Command(label = "spawn", usage = "spawn [amount] [level(monster only)]", permission = "server.spawn") public final class SpawnCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.spawn.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/StopCommand.java b/src/main/java/emu/grasscutter/command/commands/StopCommand.java index ad4903107..64326a748 100644 --- a/src/main/java/emu/grasscutter/command/commands/StopCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/StopCommand.java @@ -9,10 +9,14 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "stop", usage = "stop", - description = "Stops the server", permission = "server.stop") +@Command(label = "stop", usage = "stop", permission = "server.stop") public final class StopCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.stop.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { CommandHandler.sendMessage(null, translate("commands.stop.success")); diff --git a/src/main/java/emu/grasscutter/command/commands/TalentCommand.java b/src/main/java/emu/grasscutter/command/commands/TalentCommand.java index c9b2e8931..ca1bff76d 100644 --- a/src/main/java/emu/grasscutter/command/commands/TalentCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TalentCommand.java @@ -14,8 +14,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "talent", usage = "talent ", - description = "Set talent level for your current active character", permission = "player.settalent") +@Command(label = "talent", usage = "talent ", permission = "player.settalent") public final class TalentCommand implements CommandHandler { private void setTalentLevel(Player sender, Player player, Avatar avatar, int talentId, int talentLevel) { int oldLevel = avatar.getSkillLevelMap().get(talentId); @@ -44,6 +43,11 @@ public final class TalentCommand implements CommandHandler { CommandHandler.sendMessage(sender, translate(successMessage, talentLevel)); } + @Override + public String description() { + return translate("commands.talent.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java b/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java index 54c6101f7..d25e73f96 100644 --- a/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java @@ -10,9 +10,13 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "tpall", usage = "tpall", - description = "Teleports all players in your world to your position", permission = "player.tpall") +@Command(label = "tpall", usage = "tpall", permission = "player.tpall") public final class TeleportAllCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.teleportAll.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java b/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java index 06b669a17..364e4188f 100644 --- a/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java @@ -10,8 +10,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "teleport", usage = "teleport [scene id]", aliases = {"tp"}, - description = "Change the player's position.", permission = "player.teleport") +@Command(label = "teleport", usage = "teleport [scene id]", aliases = {"tp"}, permission = "player.teleport") public final class TeleportCommand implements CommandHandler { private float parseRelative(String input, Float current) { // TODO: Maybe this will be useful elsewhere later @@ -25,6 +24,11 @@ public final class TeleportCommand implements CommandHandler { return current; } + @Override + public String description() { + return translate("commands.teleport.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java b/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java index df8a6a01f..d2a6c5f64 100644 --- a/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java @@ -11,10 +11,14 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "weather", usage = "weather [climateId]", - description = "Changes the weather.", aliases = {"w"}, permission = "player.weather") +@Command(label = "weather", usage = "weather [climateId]", aliases = {"w"}, permission = "player.weather") public final class WeatherCommand implements CommandHandler { + @Override + public String description() { + return translate("commands.weather.description"); + } + @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index d9923a656..b5558833f 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -21,6 +21,7 @@ import com.google.gson.reflect.TypeToken; import emu.grasscutter.GameConstants; import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandMap; import emu.grasscutter.data.GameData; import emu.grasscutter.data.ResourceLoader; @@ -112,17 +113,16 @@ final class ToolsWithLanguageOption { writer.println("// Created " + dtf.format(now) + System.lineSeparator() + System.lineSeparator()); CommandMap cmdMap = new CommandMap(true); - List cmdList = new ArrayList<>(cmdMap.getAnnotationsAsList()); + HashMap cmdList = cmdMap.getHandlersAndAnnotations(); writer.println("// Commands"); - for (Command cmd : cmdList) { - String cmdName = cmd.label(); + cmdList.forEach((handler, command) -> { + String cmdName = command.label(); while (cmdName.length() <= 15) { cmdName = " " + cmdName; } - writer.println(cmdName + " : " + cmd.description()); - } - + writer.println(cmdName + " : " + handler.description()); + }); writer.println(); list = new ArrayList<>(GameData.getAvatarDataMap().keySet()); diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index fb33ba287..438674e9c 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -95,17 +95,20 @@ "create": "Account created with UID %s.", "delete": "Account deleted.", "no_account": "Account not found.", - "command_usage": "Usage: account [uid]" + "command_usage": "Usage: account [uid]", + "description": "Modify user accounts" }, "broadcast": { "command_usage": "Usage: broadcast ", - "message_sent": "Message sent." + "message_sent": "Message sent.", + "description": "Sends a message to all the players" }, "changescene": { "usage": "Usage: changescene ", "already_in_scene": "You are already in that scene.", "success": "Changed to scene %s.", - "exists_error": "The specified scene does not exist." + "exists_error": "The specified scene does not exist.", + "description": "Changes your scene" }, "clear": { "command_usage": "Usage: clear ", @@ -115,35 +118,41 @@ "furniture": "Cleared furniture for %s.", "displays": "Cleared displays for %s.", "virtuals": "Cleared virtuals for %s.", - "everything": "Cleared everything for %s." + "everything": "Cleared everything for %s.", + "description": "Deletes unequipped unlocked items, including yellow rarity ones from your inventory" }, "coop": { "usage": "Usage: coop ", - "success": "Summoned %s to %s's world." + "success": "Summoned %s to %s's world.", + "description": "Forces someone to join the world of others" }, "enter_dungeon": { "usage": "Usage: enterdungeon ", "changed": "Changed to dungeon %s", "not_found_error": "Dungeon does not exist", - "in_dungeon_error": "You are already in that dungeon" + "in_dungeon_error": "You are already in that dungeon", + "description": "Enter a dungeon" }, "giveAll": { "usage": "Usage: giveall [player] [amount]", "started": "Receiving all items...", "success": "Successfully gave all items to %s.", - "invalid_amount_or_playerId": "Invalid amount or player ID." + "invalid_amount_or_playerId": "Invalid amount or player ID.", + "description": "Gives all items" }, "giveArtifact": { "usage": "Usage: giveart|gart [player] [[,]]... [level]", "id_error": "Invalid artifact ID.", - "success": "Given %s to %s." + "success": "Given %s to %s.", + "description": "Gives the player a specified artifact" }, "giveChar": { "usage": "Usage: givechar [amount]", "given": "Given %s with level %s to %s.", "invalid_avatar_id": "Invalid avatar id.", "invalid_avatar_level": "Invalid avatar level.", - "invalid_avatar_or_player_id": "Invalid avatar or player ID." + "invalid_avatar_or_player_id": "Invalid avatar or player ID.", + "description": "Gives the player a specified character" }, "give": { "usage": "Usage: give [amount] [level]", @@ -151,29 +160,36 @@ "refinement_must_between_1_and_5": "Refinement must be between 1 and 5.", "given": "Given %s of %s to %s.", "given_with_level_and_refinement": "Given %s with level %s, refinement %s %s times to %s", - "given_level": "Given %s with level %s %s times to %s" + "given_level": "Given %s with level %s %s times to %s", + "description": "Gives an item to you or the specified player" }, "godmode": { - "success": "Godmode is now %s for %s." + "success": "Godmode is now %s for %s.", + "description": "Prevents you from taking damage. Defaults to toggle." }, "heal": { - "success": "All characters have been healed." + "success": "All characters have been healed.", + "description": "Heal all characters in your current team." }, "kick": { "player_kick_player": "Player [%s:%s] has kicked player [%s:%s]", - "server_kick_player": "Kicking player [%s:%s]" + "server_kick_player": "Kicking player [%s:%s]", + "description": "Kicks the specified player from the server (WIP)" }, "kill": { "usage": "Usage: killall [playerUid] [sceneId]", "scene_not_found_in_player_world": "Scene not found in player world", - "kill_monsters_in_scene": "Killing %s monsters in scene %s" + "kill_monsters_in_scene": "Killing %s monsters in scene %s", + "description": "Kill all entities" }, "killCharacter": { "usage": "Usage: /killcharacter [playerId]", - "success": "Killed %s's current character." + "success": "Killed %s's current character.", + "description": "Kills the players current character" }, "list": { - "success": "There are %s player(s) online:" + "success": "There are %s player(s) online:", + "description": "List online players" }, "permission": { "usage": "Usage: permission ", @@ -181,21 +197,26 @@ "has_error": "They already have this permission!", "remove": "Permission removed.", "not_have_error": "They don't have this permission!", - "account_error": "The account cannot be found." + "account_error": "The account cannot be found.", + "description": "Grants or removes a permission for a user" }, "position": { - "success": "Coordinates: %s, %s, %s\nScene id: %s" + "success": "Coordinates: %s, %s, %s\nScene id: %s", + "description": "Get coordinates." }, "reload": { "reload_start": "Reloading config.", - "reload_done": "Reload complete." + "reload_done": "Reload complete.", + "description": "Reload server config" }, "resetConst": { "reset_all": "Reset all avatars' constellations.", - "success": "Constellations for %s have been reset. Please relog to see changes." + "success": "Constellations for %s have been reset. Please relog to see changes.", + "description": "Resets the constellation level on your current active character, will need to relog after using the command to see any changes." }, "resetShopLimit": { - "usage": "Usage: /resetshop " + "usage": "Usage: /resetshop ", + "description": "Reset target player's shop refresh time." }, "sendMail": { "usage": "Usage: give [player] [amount]", @@ -217,17 +238,20 @@ "message": "", "sender": "", "arguments": " [amount] [level]", - "error": "ERROR: invalid construction stage %s. Check console for stacktrace." + "error": "ERROR: invalid construction stage %s. Check console for stacktrace.", + "description": "Sends mail to the specified user. The usage of this command changes based on it's composition state." }, "sendMessage": { "usage": "Usage: sendmessage ", - "success": "Message sent." + "success": "Message sent.", + "description": "Sends a message to a player as the server" }, "setFetterLevel": { "usage": "Usage: setfetterlevel ", "range_error": "Fetter level must be between 0 and 10.", "success": "Fetter level set to %s", - "level_error": "Invalid fetter level." + "level_error": "Invalid fetter level.", + "description": "Sets your fetter level for your current active character" }, "setStats": { "usage_console": "Usage: setstats|stats @ ", @@ -238,20 +262,24 @@ "player_error": "Player not found or offline.", "set_self": "%s set to %s.", "set_for_uid": "%s for %s set to %s.", - "set_max_hp": "MAX HP set to %s." + "set_max_hp": "MAX HP set to %s.", + "description": "Set fight property for your current active character" }, "setWorldLevel": { "usage": "Usage: setworldlevel ", "value_error": "World level must be between 0-8", "success": "World level set to %s.", - "invalid_world_level": "Invalid world level." + "invalid_world_level": "Invalid world level.", + "description": "Sets your world level (Relog to see proper effects)" }, "spawn": { "usage": "Usage: spawn [amount] [level(monster only)]", - "success": "Spawned %s of %s." + "success": "Spawned %s of %s.", + "description": "Spawns an entity near you" }, "stop": { - "success": "Server shutting down..." + "success": "Server shutting down...", + "description": "Stops the server" }, "talent": { "usage_1": "To set talent level: /talent set ", @@ -267,32 +295,41 @@ "invalid_level": "Invalid talent level.", "normal_attack_id": "Normal Attack ID %s.", "e_skill_id": "E skill ID %s.", - "q_skill_id": "Q skill ID %s." + "q_skill_id": "Q skill ID %s.", + "description": "Set talent level for your current active character" }, "teleportAll": { "success": "Summoned all players to your location.", - "error": "You only can use this command in MP mode." + "error": "You only can use this command in MP mode.", + "description": "Teleports all players in your world to your position" }, "teleport": { "usage_server": "Usage: /tp @ [scene id]", "usage": "Usage: /tp [@] [scene id]", "specify_player_id": "You must specify a player id.", "invalid_position": "Invalid position.", - "success": "Teleported %s to %s, %s, %s in scene %s" + "success": "Teleported %s to %s, %s, %s in scene %s", + "description": "Change the player's position." }, "weather": { "usage": "Usage: weather [climateId]", "success": "Changed weather to %s with climate %s", - "invalid_id": "Invalid ID." + "invalid_id": "Invalid ID.", + "description": "Changes the weather." }, "drop": { "command_usage": "Usage: drop [amount]", - "success": "Dropped %s of %s." + "success": "Dropped %s of %s.", + "description": "Drops an item near you" }, "help": { "usage": "Usage: ", "aliases": "Aliases: ", - "available_commands": "Available commands: " + "available_commands": "Available commands: ", + "description": "Sends the help message or shows information about a specified command" + }, + "restart": { + "description": "Restarts the current session" } } } From ef3579da82990111f3f0c826bfd4983655319c7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=9D=97=E5=90=9B?= Date: Sun, 8 May 2022 11:51:32 +0800 Subject: [PATCH 016/312] add Command description --- src/main/java/emu/grasscutter/command/Command.java | 2 ++ src/main/java/emu/grasscutter/command/CommandHandler.java | 2 +- .../java/emu/grasscutter/command/commands/HelpCommand.java | 6 +++--- src/main/java/emu/grasscutter/tools/Tools.java | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/Command.java b/src/main/java/emu/grasscutter/command/Command.java index c94804678..734f454ea 100644 --- a/src/main/java/emu/grasscutter/command/Command.java +++ b/src/main/java/emu/grasscutter/command/Command.java @@ -9,6 +9,8 @@ public @interface Command { String usage() default "No usage specified"; + String description() default "No description specified"; + String[] aliases() default {}; String permission() default ""; diff --git a/src/main/java/emu/grasscutter/command/CommandHandler.java b/src/main/java/emu/grasscutter/command/CommandHandler.java index dadb87bb3..76d5d7b04 100644 --- a/src/main/java/emu/grasscutter/command/CommandHandler.java +++ b/src/main/java/emu/grasscutter/command/CommandHandler.java @@ -7,7 +7,7 @@ import java.util.List; public interface CommandHandler { - String description(); + default String description() { return null; }; /** * Send a message to the target. diff --git a/src/main/java/emu/grasscutter/command/commands/HelpCommand.java b/src/main/java/emu/grasscutter/command/commands/HelpCommand.java index dbb85bf9d..323c2878b 100644 --- a/src/main/java/emu/grasscutter/command/commands/HelpCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/HelpCommand.java @@ -43,7 +43,7 @@ public final class HelpCommand implements CommandHandler { } else { Command annotation = handler.getClass().getAnnotation(Command.class); - builder.append(" ").append(handler.description()).append("\n"); + builder.append(" ").append(handler.description() == null ? annotation.description(): handler.description()).append("\n"); builder.append(translate("commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { builder.append("\n").append(translate("commands.help.aliases")); @@ -65,7 +65,7 @@ public final class HelpCommand implements CommandHandler { StringBuilder builder = new StringBuilder("\n" + translate("commands.help.available_commands") + "\n"); annotations.forEach((annotation, handler) -> { builder.append(annotation.label()).append("\n"); - builder.append(" ").append(handler.description()).append("\n"); + builder.append(" ").append(handler.description() == null ? annotation.description() : handler.description()).append("\n"); builder.append(translate("commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { builder.append("\n").append(translate("commands.help.aliases")); @@ -82,7 +82,7 @@ public final class HelpCommand implements CommandHandler { CommandHandler.sendMessage(player, translate("commands.help.available_commands")); annotations.forEach((annotation, handler) -> { StringBuilder builder = new StringBuilder(annotation.label()).append("\n"); - builder.append(" ").append(handler.description()).append("\n"); + builder.append(" ").append(handler.description() == null ? annotation.description() : handler.description()).append("\n"); builder.append(translate("commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { builder.append("\n").append(translate("commands.help.aliases")); diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index b5558833f..a1bcb1b9a 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -121,7 +121,7 @@ final class ToolsWithLanguageOption { while (cmdName.length() <= 15) { cmdName = " " + cmdName; } - writer.println(cmdName + " : " + handler.description()); + writer.println(cmdName + " : " + (handler.description() == null ? command.description() : handler.description())); }); writer.println(); From 3a5503de725a991b8bc520444c3ab1f1349ffcb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E5=9D=97=E5=90=9B?= Date: Sun, 8 May 2022 14:44:14 +0800 Subject: [PATCH 017/312] Using annotation key translation --- .../java/emu/grasscutter/command/Command.java | 2 +- .../grasscutter/command/CommandHandler.java | 2 -- .../emu/grasscutter/command/CommandMap.java | 8 ------- .../command/commands/AccountCommand.java | 7 +----- .../command/commands/BroadcastCommand.java | 7 +----- .../command/commands/ChangeSceneCommand.java | 6 +---- .../command/commands/ClearCommand.java | 6 +---- .../command/commands/CoopCommand.java | 6 +---- .../command/commands/DropCommand.java | 7 +----- .../command/commands/EnterDungeonCommand.java | 6 +---- .../command/commands/GiveAllCommand.java | 7 +----- .../command/commands/GiveArtifactCommand.java | 6 +---- .../command/commands/GiveCharCommand.java | 7 +----- .../command/commands/GiveCommand.java | 7 +----- .../command/commands/GodModeCommand.java | 7 +----- .../command/commands/HealCommand.java | 6 +---- .../command/commands/HelpCommand.java | 23 ++++++++----------- .../command/commands/KickCommand.java | 7 +----- .../command/commands/KillAllCommand.java | 7 +----- .../commands/KillCharacterCommand.java | 7 +----- .../command/commands/ListCommand.java | 7 +----- .../command/commands/PermissionCommand.java | 7 +----- .../command/commands/PositionCommand.java | 7 +----- .../command/commands/ReloadCommand.java | 7 +----- .../command/commands/ResetConstCommand.java | 7 +----- .../commands/ResetShopLimitCommand.java | 6 +---- .../command/commands/RestartCommand.java | 7 +----- .../command/commands/SendMailCommand.java | 9 ++------ .../command/commands/SendMessageCommand.java | 7 +----- .../commands/SetFetterLevelCommand.java | 7 +----- .../command/commands/SetStatsCommand.java | 7 +----- .../commands/SetWorldLevelCommand.java | 7 +----- .../command/commands/SpawnCommand.java | 7 +----- .../command/commands/StopCommand.java | 7 +----- .../command/commands/TalentCommand.java | 7 +----- .../command/commands/TeleportAllCommand.java | 6 +---- .../command/commands/TeleportCommand.java | 7 +----- .../command/commands/WeatherCommand.java | 7 +----- .../java/emu/grasscutter/tools/Tools.java | 12 ++++++---- src/main/resources/languages/en-US.json | 1 + 40 files changed, 53 insertions(+), 227 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/Command.java b/src/main/java/emu/grasscutter/command/Command.java index 734f454ea..045f6a51c 100644 --- a/src/main/java/emu/grasscutter/command/Command.java +++ b/src/main/java/emu/grasscutter/command/Command.java @@ -9,7 +9,7 @@ public @interface Command { String usage() default "No usage specified"; - String description() default "No description specified"; + String description() default "commands.generic.no_description_specified"; String[] aliases() default {}; diff --git a/src/main/java/emu/grasscutter/command/CommandHandler.java b/src/main/java/emu/grasscutter/command/CommandHandler.java index 76d5d7b04..f4fe12b3f 100644 --- a/src/main/java/emu/grasscutter/command/CommandHandler.java +++ b/src/main/java/emu/grasscutter/command/CommandHandler.java @@ -7,8 +7,6 @@ import java.util.List; public interface CommandHandler { - default String description() { return null; }; - /** * Send a message to the target. * diff --git a/src/main/java/emu/grasscutter/command/CommandMap.java b/src/main/java/emu/grasscutter/command/CommandMap.java index 6b6a852af..a183c6ac3 100644 --- a/src/main/java/emu/grasscutter/command/CommandMap.java +++ b/src/main/java/emu/grasscutter/command/CommandMap.java @@ -85,14 +85,6 @@ public final class CommandMap { return new LinkedHashMap<>(this.annotations); } - public HashMap getHandlersAndAnnotations() { - HashMap hashMap = new HashMap<>(); - this.commands.forEach((key, handler) -> { - hashMap.put(handler, this.annotations.get(key)); - }); - return hashMap; - } - /** * Returns a list of all registered commands. * diff --git a/src/main/java/emu/grasscutter/command/commands/AccountCommand.java b/src/main/java/emu/grasscutter/command/commands/AccountCommand.java index 1c5c8228e..4b287afa7 100644 --- a/src/main/java/emu/grasscutter/command/commands/AccountCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/AccountCommand.java @@ -9,14 +9,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "account", usage = "account [uid]") +@Command(label = "account", usage = "account [uid]", description = "commands.account.description") public final class AccountCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.account.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (sender != null) { diff --git a/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java b/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java index fcabb131c..95f0c7c05 100644 --- a/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java @@ -9,14 +9,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "broadcast", usage = "broadcast ", aliases = {"b"}, permission = "server.broadcast") +@Command(label = "broadcast", usage = "broadcast ", aliases = {"b"}, permission = "server.broadcast", description = "commands.broadcast.description") public final class BroadcastCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.broadcast.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (args.size() < 1) { diff --git a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java index ea7bfe0f6..594eb27c6 100644 --- a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java @@ -8,12 +8,8 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "changescene", usage = "changescene ", aliases = {"scene"}, permission = "player.changescene") +@Command(label = "changescene", usage = "changescene ", aliases = {"scene"}, permission = "player.changescene", description = "commands.changescene.description") public final class ChangeSceneCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.changescene.description"); - } @Override public void execute(Player sender, Player targetPlayer, List args) { diff --git a/src/main/java/emu/grasscutter/command/commands/ClearCommand.java b/src/main/java/emu/grasscutter/command/commands/ClearCommand.java index d416bec52..38f78e638 100644 --- a/src/main/java/emu/grasscutter/command/commands/ClearCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ClearCommand.java @@ -13,15 +13,11 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; @Command(label = "clear", usage = "clear ", //Merged /clearartifacts and /clearweapons to /clear [uid] + description = "commands.clear.description", aliases = {"clear"}, permission = "player.clearinv") public final class ClearCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.clear.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/CoopCommand.java b/src/main/java/emu/grasscutter/command/commands/CoopCommand.java index 120665b76..cf7d4fc82 100644 --- a/src/main/java/emu/grasscutter/command/commands/CoopCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/CoopCommand.java @@ -9,12 +9,8 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "coop", usage = "coop [host UID]", permission = "server.coop") +@Command(label = "coop", usage = "coop [host UID]", permission = "server.coop", description = "commands.coop.description") public final class CoopCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.coop.description"); - } @Override public void execute(Player sender, Player targetPlayer, List args) { diff --git a/src/main/java/emu/grasscutter/command/commands/DropCommand.java b/src/main/java/emu/grasscutter/command/commands/DropCommand.java index a187cfda8..10c306cad 100644 --- a/src/main/java/emu/grasscutter/command/commands/DropCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/DropCommand.java @@ -13,14 +13,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "drop", usage = "drop [amount]", aliases = {"d", "dropitem"}, permission = "server.drop") +@Command(label = "drop", usage = "drop [amount]", aliases = {"d", "dropitem"}, permission = "server.drop", description = "commands.drop.description") public final class DropCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.drop.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java b/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java index d92d537e9..8534c034f 100644 --- a/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java @@ -8,12 +8,8 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "enterdungeon", usage = "enterdungeon ", aliases = {"dungeon"}, permission = "player.enterdungeon") +@Command(label = "enterdungeon", usage = "enterdungeon ", aliases = {"dungeon"}, permission = "player.enterdungeon", description = "commands.enter_dungeon.description") public final class EnterDungeonCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.enter_dungeon.description"); - } @Override public void execute(Player sender, Player targetPlayer, List args) { diff --git a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java index 5009a4462..c94d67129 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java @@ -15,14 +15,9 @@ import java.util.*; import static emu.grasscutter.utils.Language.translate; -@Command(label = "giveall", usage = "giveall [amount]", aliases = {"givea"}, permission = "player.giveall", threading = true) +@Command(label = "giveall", usage = "giveall [amount]", aliases = {"givea"}, permission = "player.giveall", threading = true, description = "commands.giveAll.description") public final class GiveAllCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.giveAll.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java index d41e83671..b87642bb2 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java @@ -16,12 +16,8 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "giveart", usage = "giveart [[,]]... [level]", aliases = {"gart"}, permission = "player.giveart") +@Command(label = "giveart", usage = "giveart [[,]]... [level]", aliases = {"gart"}, permission = "player.giveart", description = "commands.giveArtifact.description") public final class GiveArtifactCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.giveArtifact.description"); - } @Override public void execute(Player sender, Player targetPlayer, List args) { diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java index 0e784639d..5c6bad0d2 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java @@ -12,14 +12,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "givechar", usage = "givechar [level]", aliases = {"givec"}, permission = "player.givechar") +@Command(label = "givechar", usage = "givechar [level]", aliases = {"givec"}, permission = "player.givechar", description = "commands.giveChar.description") public final class GiveCharCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.giveChar.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java index 3daa2dff3..19a9a8d26 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java @@ -17,7 +17,7 @@ import java.util.regex.Pattern; import static emu.grasscutter.utils.Language.translate; @Command(label = "give", usage = "give [amount] [level]", aliases = { - "g", "item", "giveitem"}, permission = "player.give") + "g", "item", "giveitem"}, permission = "player.give", description = "commands.give.description") public final class GiveCommand implements CommandHandler { Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java is a joke of a proglang that doesn't have raw string literals Pattern refineRegex = Pattern.compile("r(\\d+)"); @@ -31,11 +31,6 @@ public final class GiveCommand implements CommandHandler { return -1; } - @Override - public String description() { - return translate("commands.give.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java b/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java index 4fd2999a6..bf2a00c9f 100644 --- a/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java @@ -8,14 +8,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "godmode", usage = "godmode [on|off|toggle]", permission = "player.godmode") +@Command(label = "godmode", usage = "godmode [on|off|toggle]", permission = "player.godmode", description = "commands.godmode.description") public final class GodModeCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.godmode.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/HealCommand.java b/src/main/java/emu/grasscutter/command/commands/HealCommand.java index b459ecb8c..440db0a49 100644 --- a/src/main/java/emu/grasscutter/command/commands/HealCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/HealCommand.java @@ -11,12 +11,8 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "heal", usage = "heal|h", aliases = {"h"}, permission = "player.heal") +@Command(label = "heal", usage = "heal|h", aliases = {"h"}, permission = "player.heal", description = "commands.heal.description") public final class HealCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.heal.description"); - } @Override public void execute(Player sender, Player targetPlayer, List args) { diff --git a/src/main/java/emu/grasscutter/command/commands/HelpCommand.java b/src/main/java/emu/grasscutter/command/commands/HelpCommand.java index 323c2878b..8a222f7a6 100644 --- a/src/main/java/emu/grasscutter/command/commands/HelpCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/HelpCommand.java @@ -10,26 +10,21 @@ import java.util.*; import static emu.grasscutter.utils.Language.translate; -@Command(label = "help", usage = "help [command]") +@Command(label = "help", usage = "help [command]", description = "commands.help.description") public final class HelpCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.help.description"); - } - @Override public void execute(Player player, Player targetPlayer, List args) { if (args.size() < 1) { HashMap handlers = CommandMap.getInstance().getHandlers(); - HashMap annotations = new HashMap<>(); + List annotations = new ArrayList<>(); for (String key : handlers.keySet()) { Command annotation = handlers.get(key).getClass().getAnnotation(Command.class); if (!Arrays.asList(annotation.aliases()).contains(key)) { if (player != null && !Objects.equals(annotation.permission(), "") && !player.getAccount().hasPermission(annotation.permission())) continue; - annotations.put(annotation, handlers.get(key)); + annotations.add(annotation); } } @@ -43,7 +38,7 @@ public final class HelpCommand implements CommandHandler { } else { Command annotation = handler.getClass().getAnnotation(Command.class); - builder.append(" ").append(handler.description() == null ? annotation.description(): handler.description()).append("\n"); + builder.append(" ").append(translate(annotation.description())).append("\n"); builder.append(translate("commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { builder.append("\n").append(translate("commands.help.aliases")); @@ -60,12 +55,12 @@ public final class HelpCommand implements CommandHandler { } } - void SendAllHelpMessage(Player player, HashMap annotations) { + void SendAllHelpMessage(Player player, List annotations) { if (player == null) { StringBuilder builder = new StringBuilder("\n" + translate("commands.help.available_commands") + "\n"); - annotations.forEach((annotation, handler) -> { + annotations.forEach(annotation -> { builder.append(annotation.label()).append("\n"); - builder.append(" ").append(handler.description() == null ? annotation.description() : handler.description()).append("\n"); + builder.append(" ").append(translate(annotation.description())).append("\n"); builder.append(translate("commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { builder.append("\n").append(translate("commands.help.aliases")); @@ -80,9 +75,9 @@ public final class HelpCommand implements CommandHandler { CommandHandler.sendMessage(null, builder.toString()); } else { CommandHandler.sendMessage(player, translate("commands.help.available_commands")); - annotations.forEach((annotation, handler) -> { + annotations.forEach(annotation -> { StringBuilder builder = new StringBuilder(annotation.label()).append("\n"); - builder.append(" ").append(handler.description() == null ? annotation.description() : handler.description()).append("\n"); + builder.append(" ").append(translate(annotation.description())).append("\n"); builder.append(translate("commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { builder.append("\n").append(translate("commands.help.aliases")); diff --git a/src/main/java/emu/grasscutter/command/commands/KickCommand.java b/src/main/java/emu/grasscutter/command/commands/KickCommand.java index 71b487cc4..9741226e7 100644 --- a/src/main/java/emu/grasscutter/command/commands/KickCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KickCommand.java @@ -8,14 +8,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "kick", usage = "kick", permission = "server.kick") +@Command(label = "kick", usage = "kick", permission = "server.kick", description = "commands.kick.description") public final class KickCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.kick.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java b/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java index 1fa51eec4..da9ac7b5e 100644 --- a/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java @@ -12,14 +12,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "killall", usage = "killall [sceneId]", permission = "server.killall") +@Command(label = "killall", usage = "killall [sceneId]", permission = "server.killall", description = "commands.kill.description") public final class KillAllCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.kill.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java b/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java index f3fdb4998..3eda6f7e7 100644 --- a/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java @@ -13,14 +13,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"}, permission = "player.killcharacter") +@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"}, permission = "player.killcharacter", description = "commands.list.description") public final class KillCharacterCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.killCharacter.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/ListCommand.java b/src/main/java/emu/grasscutter/command/commands/ListCommand.java index 7834d2467..53a274e52 100644 --- a/src/main/java/emu/grasscutter/command/commands/ListCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ListCommand.java @@ -10,14 +10,9 @@ import java.util.Map; import static emu.grasscutter.utils.Language.translate; -@Command(label = "list", usage = "list [uid]", aliases = {"players"}) +@Command(label = "list", usage = "list [uid]", aliases = {"players"}, description = "commands.list.description") public final class ListCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.list.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { Map playersMap = Grasscutter.getGameServer().getPlayers(); diff --git a/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java b/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java index 309451945..4b945b3d1 100644 --- a/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java @@ -10,14 +10,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "permission", usage = "permission ", permission = "*") +@Command(label = "permission", usage = "permission ", permission = "*", description = "commands.permission.description") public final class PermissionCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.permission.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/PositionCommand.java b/src/main/java/emu/grasscutter/command/commands/PositionCommand.java index 3a3b40a3f..b5a250af6 100644 --- a/src/main/java/emu/grasscutter/command/commands/PositionCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/PositionCommand.java @@ -9,14 +9,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "position", usage = "position", aliases = {"pos"}) +@Command(label = "position", usage = "position", aliases = {"pos"}, description = "commands.position.description") public final class PositionCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.position.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java index 8e3e5e5aa..29eb93d0d 100644 --- a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java @@ -9,14 +9,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "reload", usage = "reload", permission = "server.reload") +@Command(label = "reload", usage = "reload", permission = "server.reload", description = "commands.reload.description") public final class ReloadCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.reload.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { CommandHandler.sendMessage(sender, translate("commands.reload.reload_start")); diff --git a/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java b/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java index 9eba8e4c4..3a77cee4d 100644 --- a/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java @@ -11,14 +11,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; @Command(label = "resetconst", usage = "resetconst [all]", - aliases = {"resetconstellation"}, permission = "player.resetconstellation") + aliases = {"resetconstellation"}, permission = "player.resetconstellation", description = "commands.resetConst.description") public final class ResetConstCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.resetConst.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java index bba8da32c..7aa84ff6a 100644 --- a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java @@ -9,12 +9,8 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "resetshop", usage = "resetshop", permission = "server.resetshop") +@Command(label = "resetshop", usage = "resetshop", permission = "server.resetshop", description = "commands.status.description") public final class ResetShopLimitCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.status.description"); - } @Override public void execute(Player sender, Player targetPlayer, List args) { diff --git a/src/main/java/emu/grasscutter/command/commands/RestartCommand.java b/src/main/java/emu/grasscutter/command/commands/RestartCommand.java index 2c56ae443..045a49d9e 100644 --- a/src/main/java/emu/grasscutter/command/commands/RestartCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/RestartCommand.java @@ -8,14 +8,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "restart", usage = "restart") +@Command(label = "restart", usage = "restart", description = "commands.restart.description") public final class RestartCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.restart.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (sender == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java b/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java index a56d68165..69aafa20b 100644 --- a/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java @@ -13,7 +13,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; @SuppressWarnings("ConstantConditions") -@Command(label = "sendmail", usage = "sendmail [templateId]", permission = "server.sendmail") +@Command(label = "sendmail", usage = "sendmail [templateId]", permission = "server.sendmail", description = "commands.sendMail.description") public final class SendMailCommand implements CommandHandler { // TODO: You should be able to do /sendmail and then just send subsequent messages until you finish @@ -23,11 +23,6 @@ public final class SendMailCommand implements CommandHandler { // Key = User that is constructing the mail. private static final HashMap mailBeingConstructed = new HashMap(); - @Override - public String description() { - return translate("commands.sendMail.description"); - } - // Yes this is awful and I hate it. @Override public void execute(Player sender, Player targetPlayer, List args) { @@ -44,7 +39,7 @@ public final class SendMailCommand implements CommandHandler { MailBuilder mailBuilder; switch (args.get(0).toLowerCase()) { case "help" -> { - CommandHandler.sendMessage(sender, this.description() + "\nUsage: " + this.getClass().getAnnotation(Command.class).usage()); + CommandHandler.sendMessage(sender, translate(this.getClass().getAnnotation(Command.class).description()) + "\nUsage: " + this.getClass().getAnnotation(Command.class).usage()); return; } case "all" -> mailBuilder = new MailBuilder(true, new Mail()); diff --git a/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java b/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java index 36e53de10..18d6264db 100644 --- a/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java @@ -9,14 +9,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; @Command(label = "say", usage = "say ", - aliases = {"sendservmsg", "sendservermessage", "sendmessage"}, permission = "server.sendmessage") + aliases = {"sendservmsg", "sendservermessage", "sendmessage"}, permission = "server.sendmessage", description = "commands.sendMessage.description") public final class SendMessageCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.sendMessage.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java b/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java index e098d99a5..ca5a3cb43 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java @@ -12,14 +12,9 @@ import emu.grasscutter.server.packet.send.PacketAvatarFetterDataNotify; import static emu.grasscutter.utils.Language.translate; @Command(label = "setfetterlevel", usage = "setfetterlevel ", - aliases = {"setfetterlvl", "setfriendship"}, permission = "player.setfetterlevel") + aliases = {"setfetterlvl", "setfriendship"}, permission = "player.setfetterlevel", description = "commands.setFetterLevel.description") public final class SetFetterLevelCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.setFetterLevel.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java index 11cb8ab80..c7ed78a58 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java @@ -15,7 +15,7 @@ import emu.grasscutter.utils.Language; import static emu.grasscutter.utils.Language.translate; -@Command(label = "setstats", usage = "setstats|stats ", aliases = {"stats"}, permission = "player.setstats") +@Command(label = "setstats", usage = "setstats|stats ", aliases = {"stats"}, permission = "player.setstats", description = "commands.setStats.description") public final class SetStatsCommand implements CommandHandler { static class Stat { String name; @@ -173,11 +173,6 @@ public final class SetStatsCommand implements CommandHandler { stats.put("_nonextra_physical_add_hurt", new Stat("NONEXTRA_PHYSICAL_ADD_HURT", FightProperty.FIGHT_PROP_NONEXTRA_PHYSICAL_ADD_HURT, true)); } - @Override - public String description() { - return translate("commands.setStats.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { String syntax = sender == null ? translate("commands.setStats.usage_console") : translate("commands.setStats.ingame"); diff --git a/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java b/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java index 16b10bc6c..41b959336 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java @@ -10,14 +10,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; @Command(label = "setworldlevel", usage = "setworldlevel ", - aliases = {"setworldlvl"}, permission = "player.setworldlevel") + aliases = {"setworldlvl"}, permission = "player.setworldlevel", description = "commands.setWorldLevel.description") public final class SetWorldLevelCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.setWorldLevel.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java b/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java index 8a995402b..7f0c704c6 100644 --- a/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java @@ -22,14 +22,9 @@ import java.util.Random; import static emu.grasscutter.utils.Language.translate; -@Command(label = "spawn", usage = "spawn [amount] [level(monster only)]", permission = "server.spawn") +@Command(label = "spawn", usage = "spawn [amount] [level(monster only)]", permission = "server.spawn", description = "commands.spawn.description") public final class SpawnCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.spawn.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/StopCommand.java b/src/main/java/emu/grasscutter/command/commands/StopCommand.java index 64326a748..129b27b24 100644 --- a/src/main/java/emu/grasscutter/command/commands/StopCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/StopCommand.java @@ -9,14 +9,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "stop", usage = "stop", permission = "server.stop") +@Command(label = "stop", usage = "stop", permission = "server.stop", description = "commands.stop.description") public final class StopCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.stop.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { CommandHandler.sendMessage(null, translate("commands.stop.success")); diff --git a/src/main/java/emu/grasscutter/command/commands/TalentCommand.java b/src/main/java/emu/grasscutter/command/commands/TalentCommand.java index ca1bff76d..1540a81f8 100644 --- a/src/main/java/emu/grasscutter/command/commands/TalentCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TalentCommand.java @@ -14,7 +14,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "talent", usage = "talent ", permission = "player.settalent") +@Command(label = "talent", usage = "talent ", permission = "player.settalent", description = "commands.talent.description") public final class TalentCommand implements CommandHandler { private void setTalentLevel(Player sender, Player player, Avatar avatar, int talentId, int talentLevel) { int oldLevel = avatar.getSkillLevelMap().get(talentId); @@ -43,11 +43,6 @@ public final class TalentCommand implements CommandHandler { CommandHandler.sendMessage(sender, translate(successMessage, talentLevel)); } - @Override - public String description() { - return translate("commands.talent.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java b/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java index d25e73f96..175f69b81 100644 --- a/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java @@ -10,12 +10,8 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "tpall", usage = "tpall", permission = "player.tpall") +@Command(label = "tpall", usage = "tpall", permission = "player.tpall", description = "commands.teleportAll.description") public final class TeleportAllCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.teleportAll.description"); - } @Override public void execute(Player sender, Player targetPlayer, List args) { diff --git a/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java b/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java index 364e4188f..0d15b55af 100644 --- a/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java @@ -10,7 +10,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "teleport", usage = "teleport [scene id]", aliases = {"tp"}, permission = "player.teleport") +@Command(label = "teleport", usage = "teleport [scene id]", aliases = {"tp"}, permission = "player.teleport", description = "commands.teleport.description") public final class TeleportCommand implements CommandHandler { private float parseRelative(String input, Float current) { // TODO: Maybe this will be useful elsewhere later @@ -24,11 +24,6 @@ public final class TeleportCommand implements CommandHandler { return current; } - @Override - public String description() { - return translate("commands.teleport.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java b/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java index d2a6c5f64..dd0002790 100644 --- a/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java @@ -11,14 +11,9 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "weather", usage = "weather [climateId]", aliases = {"w"}, permission = "player.weather") +@Command(label = "weather", usage = "weather [climateId]", aliases = {"w"}, permission = "player.weather", description = "commands.weather.description") public final class WeatherCommand implements CommandHandler { - @Override - public String description() { - return translate("commands.weather.description"); - } - @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index a1bcb1b9a..f69aafc80 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -31,6 +31,8 @@ import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.data.def.SceneData; import emu.grasscutter.utils.Utils; +import static emu.grasscutter.utils.Language.translate; + public final class Tools { public static void createGmHandbook() throws Exception { ToolsWithLanguageOption.createGmHandbook(getLanguageOption()); @@ -113,16 +115,16 @@ final class ToolsWithLanguageOption { writer.println("// Created " + dtf.format(now) + System.lineSeparator() + System.lineSeparator()); CommandMap cmdMap = new CommandMap(true); - HashMap cmdList = cmdMap.getHandlersAndAnnotations(); + List cmdList = new ArrayList<>(cmdMap.getAnnotationsAsList()); writer.println("// Commands"); - cmdList.forEach((handler, command) -> { - String cmdName = command.label(); + for (Command cmd : cmdList) { + String cmdName = cmd.label(); while (cmdName.length() <= 15) { cmdName = " " + cmdName; } - writer.println(cmdName + " : " + (handler.description() == null ? command.description() : handler.description())); - }); + writer.println(cmdName + " : " + translate(cmd.description())); + } writer.println(); list = new ArrayList<>(GameData.getAvatarDataMap().keySet()); diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 438674e9c..9a7485608 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -56,6 +56,7 @@ "console_execute_error": "This command can only be run from the console.", "player_execute_error": "Run this command in-game.", "command_exist_error": "No command found.", + "no_description_specified": "No description specified", "invalid": { "amount": "Invalid amount.", "artifactId": "Invalid artifactId.", From 4b6842f0069a5aeb7e6f858dd4bedf81f3063b97 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Sun, 8 May 2022 17:11:02 +0800 Subject: [PATCH 018/312] Persist Tower Data && Set The Tower Schedule --- build.gradle | 2 +- data/TowerSchedule.json | 5 ++ .../command/commands/UnlockTowerCommand.java | 32 +++++++ .../java/emu/grasscutter/data/GameData.java | 4 + .../data/def/TowerScheduleData.java | 70 ++++++++++++++++ .../dungeons/TowerDungeonSettleListener.java | 16 ++-- .../game/tower/TowerLevelRecord.java | 64 ++++++++++++++ .../grasscutter/game/tower/TowerManager.java | 83 +++++++++++++++---- .../game/tower/TowerScheduleConfig.java | 35 ++++++++ .../game/tower/TowerScheduleManager.java | 75 +++++++++++++++++ .../scripts/SceneScriptManager.java | 29 ++++--- .../emu/grasscutter/scripts/ScriptLib.java | 32 ++++--- .../grasscutter/server/game/GameServer.java | 8 +- .../packet/recv/HandlerTowerAllDataReq.java | 5 +- .../send/PacketDungeonSettleNotify.java | 2 +- .../packet/send/PacketTowerAllDataRsp.java | 57 +++++++++---- .../PacketTowerFloorRecordChangeNotify.java | 6 +- .../send/PacketTowerLevelStarCondNotify.java | 32 +++++++ .../emu/grasscutter/utils/DateHelper.java | 6 +- src/main/resources/languages/en-US.json | 3 + 20 files changed, 500 insertions(+), 66 deletions(-) create mode 100644 data/TowerSchedule.json create mode 100644 src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java create mode 100644 src/main/java/emu/grasscutter/data/def/TowerScheduleData.java create mode 100644 src/main/java/emu/grasscutter/game/tower/TowerLevelRecord.java create mode 100644 src/main/java/emu/grasscutter/game/tower/TowerScheduleConfig.java create mode 100644 src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketTowerLevelStarCondNotify.java diff --git a/build.gradle b/build.gradle index 8c9257777..2f19e9c99 100644 --- a/build.gradle +++ b/build.gradle @@ -70,7 +70,7 @@ dependencies { implementation group: 'io.netty', name: 'netty-all', version: '4.1.71.Final' - implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.8' + implementation group: 'com.google.code.gson', name: 'gson', version: '2.9.0' implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '3.18.2' implementation group: 'org.reflections', name: 'reflections', version: '0.10.2' diff --git a/data/TowerSchedule.json b/data/TowerSchedule.json new file mode 100644 index 000000000..b93100645 --- /dev/null +++ b/data/TowerSchedule.json @@ -0,0 +1,5 @@ +{ + "scheduleId" : 1, + "scheduleStartTime" : "2022-05-01T00:00:00+08:00", + "nextScheduleChangeTime" : "2022-05-30T00:00:00+08:00" +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java b/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java new file mode 100644 index 000000000..e0fce695c --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java @@ -0,0 +1,32 @@ +package emu.grasscutter.command.commands; + +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.tower.TowerLevelRecord; + +import java.util.List; + +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "unlocktower", usage = "unlocktower", aliases = {"ut"}, + description = "Unlock all levels of tower", permission = "player.tower") +public class UnlockTowerCommand implements CommandHandler { + + @Override + public void execute(Player sender, Player targetPlayer, List args) { + unlockFloor(sender, sender.getServer().getTowerScheduleManager() + .getCurrentTowerScheduleData().getEntranceFloorId()); + + unlockFloor(sender, sender.getServer().getTowerScheduleManager() + .getScheduleFloors()); + + CommandHandler.sendMessage(sender, translate("commands.tower.unlock_done")); + } + + public void unlockFloor(Player player, List floors){ + floors.stream() + .filter(id -> !player.getTowerManager().getRecordMap().containsKey(id)) + .forEach(id -> player.getTowerManager().getRecordMap().put(id, new TowerLevelRecord(id))); + } +} diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 76a7f1652..fb3991ad7 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -70,6 +70,7 @@ public class GameData { private static final Int2ObjectMap rewardPreviewDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap towerFloorDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap towerLevelDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap towerScheduleDataMap = new Int2ObjectOpenHashMap<>(); // Cache private static Map> fetters = new HashMap<>(); @@ -320,4 +321,7 @@ public class GameData { public static Int2ObjectMap getTowerLevelDataMap(){ return towerLevelDataMap; } + public static Int2ObjectMap getTowerScheduleDataMap(){ + return towerScheduleDataMap; + } } diff --git a/src/main/java/emu/grasscutter/data/def/TowerScheduleData.java b/src/main/java/emu/grasscutter/data/def/TowerScheduleData.java new file mode 100644 index 000000000..017776c06 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/TowerScheduleData.java @@ -0,0 +1,70 @@ +package emu.grasscutter.data.def; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; + +import java.util.List; + +@ResourceType(name = "TowerScheduleExcelConfigData.json") +public class TowerScheduleData extends GameResource { + private int ScheduleId; + private List EntranceFloorId; + private List Schedules; + private int MonthlyLevelConfigId; + @Override + public int getId() { + return ScheduleId; + } + + @Override + public void onLoad() { + super.onLoad(); + this.Schedules = this.Schedules.stream() + .filter(item -> item.getFloorList().size() > 0) + .toList(); + } + + public int getScheduleId() { + return ScheduleId; + } + + public void setScheduleId(int scheduleId) { + ScheduleId = scheduleId; + } + + public List getEntranceFloorId() { + return EntranceFloorId; + } + + public void setEntranceFloorId(List entranceFloorId) { + EntranceFloorId = entranceFloorId; + } + + public List getSchedules() { + return Schedules; + } + + public void setSchedules(List schedules) { + Schedules = schedules; + } + + public int getMonthlyLevelConfigId() { + return MonthlyLevelConfigId; + } + + public void setMonthlyLevelConfigId(int monthlyLevelConfigId) { + MonthlyLevelConfigId = monthlyLevelConfigId; + } + + public static class ScheduleDetail{ + private List FloorList; + + public List getFloorList() { + return FloorList; + } + + public void setFloorList(List floorList) { + FloorList = floorList; + } + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java b/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java index 5b1ff7a30..c480d047f 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java +++ b/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java @@ -12,12 +12,18 @@ public class TowerDungeonSettleListener implements DungeonSettleListener { scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000); var towerManager = scene.getPlayers().get(0).getTowerManager(); - towerManager.notifyCurLevelRecordChangeWhenDone(); - scene.broadcastPacket(new PacketTowerFloorRecordChangeNotify(towerManager.getCurrentFloorId())); - scene.broadcastPacket(new PacketDungeonSettleNotify(scene.getChallenge(), - true, + towerManager.notifyCurLevelRecordChangeWhenDone(3); + scene.broadcastPacket(new PacketTowerFloorRecordChangeNotify( + towerManager.getCurrentFloorId(), + 3, + towerManager.canEnterScheduleFloor() + )); + + scene.broadcastPacket(new PacketDungeonSettleNotify( + scene.getChallenge(), + towerManager.hasNextFloor(), towerManager.hasNextLevel(), - towerManager.getNextFloorId() + towerManager.hasNextLevel() ? 0 : towerManager.getNextFloorId() )); } diff --git a/src/main/java/emu/grasscutter/game/tower/TowerLevelRecord.java b/src/main/java/emu/grasscutter/game/tower/TowerLevelRecord.java new file mode 100644 index 000000000..5a65f63ed --- /dev/null +++ b/src/main/java/emu/grasscutter/game/tower/TowerLevelRecord.java @@ -0,0 +1,64 @@ +package emu.grasscutter.game.tower; + +import dev.morphia.annotations.Entity; + +import java.util.HashMap; +import java.util.Map; + +@Entity +public class TowerLevelRecord { + /** + * floorId in config + */ + private int floorId; + /** + * LevelId - Stars + */ + private Map passedLevelMap; + + private int floorStarRewardProgress; + + public TowerLevelRecord setLevelStars(int levelId, int stars){ + passedLevelMap.put(levelId, stars); + return this; + } + + public int getStarCount() { + return passedLevelMap.values().stream().mapToInt(Integer::intValue).sum(); + } + + public TowerLevelRecord(){ + + } + + public TowerLevelRecord(int floorId){ + this.floorId = floorId; + this.passedLevelMap = new HashMap<>(); + this.floorStarRewardProgress = 0; + } + + public int getFloorId() { + return floorId; + } + + public void setFloorId(int floorId) { + this.floorId = floorId; + } + + public Map getPassedLevelMap() { + return passedLevelMap; + } + + public void setPassedLevelMap(Map passedLevelMap) { + this.passedLevelMap = passedLevelMap; + } + + public int getFloorStarRewardProgress() { + return floorStarRewardProgress; + } + + public void setFloorStarRewardProgress(int floorStarRewardProgress) { + this.floorStarRewardProgress = floorStarRewardProgress; + } + +} diff --git a/src/main/java/emu/grasscutter/game/tower/TowerManager.java b/src/main/java/emu/grasscutter/game/tower/TowerManager.java index 409549a1f..9346ffead 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerManager.java @@ -9,10 +9,12 @@ import emu.grasscutter.game.dungeons.TowerDungeonSettleListener; import emu.grasscutter.game.player.Player; import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; import emu.grasscutter.server.packet.send.PacketTowerCurLevelRecordChangeNotify; - import emu.grasscutter.server.packet.send.PacketTowerEnterLevelRsp; +import emu.grasscutter.server.packet.send.PacketTowerLevelStarCondNotify; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Entity public class TowerManager { @@ -26,11 +28,19 @@ public class TowerManager { this.player = player; } + /** + * the floor players chose + */ private int currentFloorId; private int currentLevel; @Transient private int currentLevelId; + /** + * floorId - Record + */ + private Map recordMap; + @Transient private int entryScene; @@ -38,7 +48,26 @@ public class TowerManager { return currentFloorId; } + public int getCurrentLevelId(){ + return this.currentLevelId + currentLevel; + } + + /** + * form 1-3 + */ + public int getCurrentLevel(){ + return currentLevel + 1; + } private static final List towerDungeonSettleListener = List.of(new TowerDungeonSettleListener()); + + public Map getRecordMap() { + if(recordMap == null){ + recordMap = new HashMap<>(); + recordMap.put(1001, new TowerLevelRecord(1001)); + } + return recordMap; + } + public void teamSelect(int floor, List> towerTeams) { var floorData = GameData.getTowerFloorDataMap().get(floor); @@ -54,51 +83,73 @@ public class TowerManager { entryScene = player.getSceneId(); } - player.getTeamManager().setupTemporaryTeam(towerTeams); } public void enterLevel(int enterPointId) { - var levelData = GameData.getTowerLevelDataMap().get(currentLevelId + currentLevel); + var levelData = GameData.getTowerLevelDataMap().get(getCurrentLevelId()); - this.currentLevel++; - var id = levelData.getDungeonId(); + var dungeonId = levelData.getDungeonId(); notifyCurLevelRecordChange(); // use team user choose player.getTeamManager().useTemporaryTeam(0); - player.getServer().getDungeonManager().handoffDungeon(player, id, + player.getServer().getDungeonManager().handoffDungeon(player, dungeonId, towerDungeonSettleListener); // make sure user can exit dungeon correctly player.getScene().setPrevScene(entryScene); player.getScene().setPrevScenePoint(enterPointId); - player.getSession().send(new PacketTowerEnterLevelRsp(currentFloorId, currentLevel)); + player.getSession().send(new PacketTowerEnterLevelRsp(currentFloorId, getCurrentLevel())); // stop using skill player.getSession().send(new PacketCanUseSkillNotify(false)); + // notify the cond of stars + player.getSession().send(new PacketTowerLevelStarCondNotify(currentFloorId, getCurrentLevel())); } public void notifyCurLevelRecordChange(){ - player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, currentLevel)); + player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, getCurrentLevel())); } - public void notifyCurLevelRecordChangeWhenDone(){ - player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, currentLevel + 1)); + public void notifyCurLevelRecordChangeWhenDone(int stars){ + if(!recordMap.containsKey(currentFloorId)){ + recordMap.put(currentFloorId, + new TowerLevelRecord(currentFloorId).setLevelStars(getCurrentLevelId(),stars)); + }else{ + recordMap.put(currentFloorId, + recordMap.get(currentFloorId).setLevelStars(getCurrentLevelId(),stars)); + } + + this.currentLevel++; + + if(!hasNextLevel()){ + // set up the next floor + recordMap.put(getNextFloorId(), new TowerLevelRecord(getNextFloorId())); + player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(getNextFloorId(), 1)); + }else{ + player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, getCurrentLevel())); + } } public boolean hasNextLevel(){ return this.currentLevel < 3; } - public int getNextFloorId() { - if(hasNextLevel()){ - return 0; - } - this.currentFloorId++; - return this.currentFloorId; + return player.getServer().getTowerScheduleManager().getNextFloorId(this.currentFloorId); + } + public boolean hasNextFloor(){ + return player.getServer().getTowerScheduleManager().getNextFloorId(this.currentFloorId) > 0; } public void clearEntry() { this.entryScene = 0; } + + public boolean canEnterScheduleFloor(){ + if(!recordMap.containsKey(player.getServer().getTowerScheduleManager().getLastEntranceFloor())){ + return false; + } + return recordMap.get(player.getServer().getTowerScheduleManager().getLastEntranceFloor()) + .getStarCount() >= 6; + } } diff --git a/src/main/java/emu/grasscutter/game/tower/TowerScheduleConfig.java b/src/main/java/emu/grasscutter/game/tower/TowerScheduleConfig.java new file mode 100644 index 000000000..35afbc7ba --- /dev/null +++ b/src/main/java/emu/grasscutter/game/tower/TowerScheduleConfig.java @@ -0,0 +1,35 @@ +package emu.grasscutter.game.tower; + +import java.util.Date; + +public class TowerScheduleConfig { + private int scheduleId; + + private Date scheduleStartTime; + private Date nextScheduleChangeTime; + + + public int getScheduleId() { + return scheduleId; + } + + public void setScheduleId(int scheduleId) { + this.scheduleId = scheduleId; + } + + public Date getScheduleStartTime() { + return scheduleStartTime; + } + + public void setScheduleStartTime(Date scheduleStartTime) { + this.scheduleStartTime = scheduleStartTime; + } + + public Date getNextScheduleChangeTime() { + return nextScheduleChangeTime; + } + + public void setNextScheduleChangeTime(Date nextScheduleChangeTime) { + this.nextScheduleChangeTime = nextScheduleChangeTime; + } +} diff --git a/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java b/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java new file mode 100644 index 000000000..33f5c158d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java @@ -0,0 +1,75 @@ +package emu.grasscutter.game.tower; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.TowerScheduleData; +import emu.grasscutter.server.game.GameServer; + +import java.io.FileReader; +import java.util.List; + +public class TowerScheduleManager { + private final GameServer gameServer; + + public GameServer getGameServer() { + return gameServer; + } + + public TowerScheduleManager(GameServer gameServer) { + this.gameServer = gameServer; + this.load(); + } + + private TowerScheduleConfig towerScheduleConfig; + + public synchronized void load(){ + try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "TowerSchedule.json")) { + towerScheduleConfig = Grasscutter.getGsonFactory().fromJson(fileReader, TowerScheduleConfig.class); + + } catch (Exception e) { + Grasscutter.getLogger().error("Unable to load tower schedule config.", e); + } + } + + public TowerScheduleConfig getTowerScheduleConfig() { + return towerScheduleConfig; + } + + public TowerScheduleData getCurrentTowerScheduleData(){ + var data = GameData.getTowerScheduleDataMap().get(towerScheduleConfig.getScheduleId()); + if(data == null){ + Grasscutter.getLogger().error("Could not get current tower schedule data by config:{}", towerScheduleConfig); + } + return data; + } + + public List getScheduleFloors() { + return getCurrentTowerScheduleData().getSchedules().get(0).getFloorList(); + } + + public int getNextFloorId(int floorId){ + var entranceFloors = getCurrentTowerScheduleData().getEntranceFloorId(); + var nextId = 0; + // find in entrance floors first + for(int i=0;i getBlocks() { return blocks; } @@ -237,16 +241,16 @@ public class SceneScriptManager { for (SceneSuite suite : group.suites) { suite.sceneMonsters = new ArrayList<>(suite.monsters.size()); - for (int id : suite.monsters) { - try { - SceneMonster monster = (SceneMonster) map.get(id); - if (monster != null) { - suite.sceneMonsters.add(monster); + suite.monsters.forEach(id -> { + Object objEntry = map.get(id.intValue()); + if (objEntry instanceof Map.Entry monsterEntry) { + Object monster = monsterEntry.getValue(); + if(monster instanceof SceneMonster sceneMonster){ + suite.sceneMonsters.add(sceneMonster); } - } catch (Exception e) { - continue; } - } + }); + suite.sceneGadgets = new ArrayList<>(suite.gadgets.size()); for (int id : suite.gadgets) { try { @@ -320,13 +324,15 @@ public class SceneScriptManager { } public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) { - this.currentGroup = group; - this.monsterSceneLimit = 0; var suite = group.getSuiteByIndex(suiteIndex); if(suite == null){ return; } - suite.sceneMonsters.forEach(mob -> spawnMonstersInGroup(group, mob)); + if(suite.sceneMonsters.size() > 0){ + this.currentGroup = group; + this.monsterSceneLimit = 0; + suite.sceneMonsters.forEach(mob -> spawnMonstersInGroup(group, mob)); + } } public void spawnMonstersInGroup(SceneGroup group) { @@ -401,6 +407,7 @@ public class SceneScriptManager { spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); }else if(this.monsterAlive.get() == 0){ // spawn the last turn of monsters + //callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs()); while(!this.monsterOrders.isEmpty()){ spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index 1b9badc11..1c4bbd0f2 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -118,7 +118,7 @@ public class ScriptLib { challengeIndex,groupId,ordersConfigId,tideCount,sceneLimit,param6); SceneGroup group = getSceneScriptManager().getGroupById(groupId); - + if (group == null || group.monsters == null) { return 1; } @@ -136,8 +136,7 @@ public class ScriptLib { if (group == null || group.monsters == null) { return 1; } - - // TODO just spawn all from group for now + this.getSceneScriptManager().spawnMonstersInGroup(group, suite); return 0; @@ -159,7 +158,13 @@ public class ScriptLib { if (group == null || group.monsters == null) { return 1; } - + + if(getSceneScriptManager().getScene().getChallenge() != null && + getSceneScriptManager().getScene().getChallenge().inProgress()) + { + return 0; + } + DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(), group); challenge.setChallengeId(challengeId); challenge.setChallengeIndex(challengeIndex); @@ -249,7 +254,7 @@ public class ScriptLib { var1); return (int) getSceneScriptManager().getScene().getEntities().values().stream() - .filter(e -> e instanceof EntityMonster) + .filter(e -> e instanceof EntityMonster && e.getGroupId() == getSceneScriptManager().getCurrentGroup().id) .count(); } public int SetMonsterBattleByGroup(int var1, int var2, int var3){ @@ -266,13 +271,11 @@ public class ScriptLib { return 0; } // 8-1 - public int GetGroupVariableValueByGroup(int var1, String var2, int var3){ - logger.debug("[LUA] Call GetGroupVariableValueByGroup with {},{},{}", - var1,var2,var3); + public int GetGroupVariableValueByGroup(String name, int groupId){ + logger.debug("[LUA] Call GetGroupVariableValueByGroup with {},{}", + name,groupId); - //TODO - - return getSceneScriptManager().getVariables().getOrDefault(var2, 0); + return getSceneScriptManager().getVariables().getOrDefault(name, 0); } public int SetIsAllowUseSkill(int canUse, int var2){ @@ -299,4 +302,11 @@ public class ScriptLib { return 0; } + public int SetGroupVariableValueByGroup(String key, int value, int groupId){ + logger.debug("[LUA] Call SetGroupVariableValueByGroup with {},{},{}", + key,value,groupId); + + return 0; + } + } diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index 7ce8488ef..cb0e4965d 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -15,6 +15,7 @@ import emu.grasscutter.game.managers.InventoryManager; import emu.grasscutter.game.managers.MultiplayerManager; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.shop.ShopManager; +import emu.grasscutter.game.tower.TowerScheduleManager; import emu.grasscutter.game.world.World; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; @@ -54,6 +55,7 @@ public final class GameServer extends KcpServer { private final DropManager dropManager; private final CombineManger combineManger; + private final TowerScheduleManager towerScheduleManager; public GameServer() { this(new InetSocketAddress( @@ -82,7 +84,7 @@ public final class GameServer extends KcpServer { this.dropManager = new DropManager(this); this.expeditionManager = new ExpeditionManager(this); this.combineManger = new CombineManger(this); - + this.towerScheduleManager = new TowerScheduleManager(this); // Hook into shutdown event. Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown)); } @@ -139,6 +141,10 @@ public final class GameServer extends KcpServer { return this.combineManger; } + public TowerScheduleManager getTowerScheduleManager() { + return towerScheduleManager; + } + public TaskMap getTaskMap() { return this.taskMap; } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerAllDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerAllDataReq.java index 2a9ef2004..38462882f 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerAllDataReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerAllDataReq.java @@ -11,7 +11,10 @@ public class HandlerTowerAllDataReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - session.send(new PacketTowerAllDataRsp()); + session.send(new PacketTowerAllDataRsp( + session.getServer().getTowerScheduleManager(), + session.getPlayer().getTowerManager() + )); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java index 479029243..56d844d8d 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java @@ -46,7 +46,7 @@ public class PacketDungeonSettleNotify extends BasePacket { .setCount(1000) .build()) ; - if(nextFloorId > 0){ + if(nextFloorId > 0 && canJump){ towerLevelEndNotify.setNextFloorId(nextFloorId); } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java index d2d2376e6..654aa4a07 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java @@ -1,37 +1,64 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.def.TowerFloorData; +import emu.grasscutter.game.tower.TowerManager; +import emu.grasscutter.game.tower.TowerScheduleManager; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.TowerAllDataRspOuterClass.TowerAllDataRsp; import emu.grasscutter.net.proto.TowerCurLevelRecordOuterClass.TowerCurLevelRecord; import emu.grasscutter.net.proto.TowerFloorRecordOuterClass.TowerFloorRecord; +import emu.grasscutter.net.proto.TowerLevelRecordOuterClass; +import emu.grasscutter.utils.DateHelper; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.IntStream; public class PacketTowerAllDataRsp extends BasePacket { - public PacketTowerAllDataRsp() { + public PacketTowerAllDataRsp(TowerScheduleManager towerScheduleManager, TowerManager towerManager) { super(PacketOpcodes.TowerAllDataRsp); - var list = GameData.getTowerFloorDataMap().values().stream() - .map(TowerFloorData::getFloorId) - .map(id -> TowerFloorRecord.newBuilder().setFloorId(id).build()) - .collect(Collectors.toList()); + var recordList = towerManager.getRecordMap().values().stream() + .map(rec -> TowerFloorRecord.newBuilder() + .setFloorId(rec.getFloorId()) + .setFloorStarRewardProgress(rec.getFloorStarRewardProgress()) + .putAllPassedLevelMap(rec.getPassedLevelMap()) + .addAllPassedLevelRecordList(buildFromPassedLevelMap(rec.getPassedLevelMap())) + .build() + ) + .toList(); + + var openTimeMap = towerScheduleManager.getScheduleFloors().stream() + .collect(Collectors.toMap(x -> x, + y -> DateHelper.getUnixTime(towerScheduleManager.getTowerScheduleConfig() + .getScheduleStartTime())) + ); TowerAllDataRsp proto = TowerAllDataRsp.newBuilder() - .setTowerScheduleId(29) - .addAllTowerFloorRecordList(list) + .setTowerScheduleId(towerScheduleManager.getCurrentTowerScheduleData().getScheduleId()) + .addAllTowerFloorRecordList(recordList) .setCurLevelRecord(TowerCurLevelRecord.newBuilder().setIsEmpty(true)) - .setNextScheduleChangeTime(Integer.MAX_VALUE) - .putFloorOpenTimeMap(1024, 1630486800) - .putFloorOpenTimeMap(1025, 1630486800) - .putFloorOpenTimeMap(1026, 1630486800) - .putFloorOpenTimeMap(1027, 1630486800) - .setScheduleStartTime(1630486800) + .setScheduleStartTime(DateHelper.getUnixTime(towerScheduleManager.getTowerScheduleConfig() + .getScheduleStartTime())) + .setNextScheduleChangeTime(DateHelper.getUnixTime(towerScheduleManager.getTowerScheduleConfig() + .getNextScheduleChangeTime())) + .putAllFloorOpenTimeMap(openTimeMap) + .setIsFinishedEntranceFloor(towerManager.canEnterScheduleFloor()) .build(); this.setData(proto); } + + private List buildFromPassedLevelMap(Map map){ + return map.entrySet().stream() + .map(item -> TowerLevelRecordOuterClass.TowerLevelRecord.newBuilder() + .setLevelId(item.getKey()) + .addAllSatisfiedCondList(IntStream.range(1, item.getValue() + 1).boxed().toList()) + .build()) + .toList(); + + } + } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerFloorRecordChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerFloorRecordChangeNotify.java index c0ed414a8..5ab091901 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerFloorRecordChangeNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerFloorRecordChangeNotify.java @@ -8,13 +8,13 @@ import emu.grasscutter.net.proto.TowerLevelRecordOuterClass.TowerLevelRecord; public class PacketTowerFloorRecordChangeNotify extends BasePacket { - public PacketTowerFloorRecordChangeNotify(int floorId) { + public PacketTowerFloorRecordChangeNotify(int floorId, int stars, boolean canEnterScheduleFloor) { super(PacketOpcodes.TowerFloorRecordChangeNotify); TowerFloorRecordChangeNotify proto = TowerFloorRecordChangeNotify.newBuilder() .addTowerFloorRecordList(TowerFloorRecord.newBuilder() .setFloorId(floorId) - .setFloorStarRewardProgress(3) + .setFloorStarRewardProgress(stars) .addPassedLevelRecordList(TowerLevelRecord.newBuilder() .setLevelId(1) .addSatisfiedCondList(1) @@ -22,7 +22,7 @@ public class PacketTowerFloorRecordChangeNotify extends BasePacket { .addSatisfiedCondList(3) .build()) .build()) - .setIsFinishedEntranceFloor(true) + .setIsFinishedEntranceFloor(canEnterScheduleFloor) .build(); this.setData(proto); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerLevelStarCondNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerLevelStarCondNotify.java new file mode 100644 index 000000000..c2c301e4e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerLevelStarCondNotify.java @@ -0,0 +1,32 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TowerLevelStarCondDataOuterClass.TowerLevelStarCondData; +import emu.grasscutter.net.proto.TowerLevelStarCondNotifyOuterClass.TowerLevelStarCondNotify; + +public class PacketTowerLevelStarCondNotify extends BasePacket { + + public PacketTowerLevelStarCondNotify(int floorId, int levelIndex) { + super(PacketOpcodes.TowerLevelStarCondNotify); + + TowerLevelStarCondNotify proto = TowerLevelStarCondNotify.newBuilder() + .setFloorId(floorId) + .setLevelIndex(levelIndex) + .addCondDataList(TowerLevelStarCondData.newBuilder() + .setCondValue(1) + .build() + ) + .addCondDataList(TowerLevelStarCondData.newBuilder() + .setCondValue(2) + .build() + ) + .addCondDataList(TowerLevelStarCondData.newBuilder() + .setCondValue(3) + .build() + ) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/utils/DateHelper.java b/src/main/java/emu/grasscutter/utils/DateHelper.java index 7005d9457..1f1393760 100644 --- a/src/main/java/emu/grasscutter/utils/DateHelper.java +++ b/src/main/java/emu/grasscutter/utils/DateHelper.java @@ -1,7 +1,7 @@ package emu.grasscutter.utils; -import java.util.Date; import java.util.Calendar; +import java.util.Date; public final class DateHelper { public static Date onlyYearMonthDay(Date now) { @@ -13,4 +13,8 @@ public final class DateHelper { calendar.set(Calendar.MILLISECOND, 0); return calendar.getTime(); } + + public static int getUnixTime(Date localDateTime){ + return (int)(localDateTime.getTime() / 1000L); + } } diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 48ffb5b0b..65a8634f7 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -280,6 +280,9 @@ "invalid_position": "Invalid position.", "success": "Teleported %s to %s, %s, %s in scene %s" }, + "tower": { + "unlock_done": "Abyss Corridor's Floors are all unlocked now." + }, "weather": { "usage": "Usage: weather [climateId]", "success": "Changed weather to %s with climate %s", From 7c35c51a6afd29bfa0731b308f9100e80182a9c6 Mon Sep 17 00:00:00 2001 From: HotaruYS <105128850+HotaruYS@users.noreply.github.com> Date: Sun, 8 May 2022 12:48:06 +0200 Subject: [PATCH 019/312] Override server logging level with environment variable (#653) Use `LOG_LEVEL` environment variable to override logging level for `emu.grasscutter` (which also contains all loggers under it). This might help with debugging various issues reported by users. Previously, the only way to override these levels would be to use `-Dlogback.configurationFile` --- src/main/resources/logback.xml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 1fc6831cb..bd0740fca 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,4 +1,6 @@ + + [%d{HH:mm:ss}] [%highlight(%level)] %msg%n @@ -14,11 +16,12 @@ %d{yyyy-MM-dd'T'HH:mm:ss'Z'} - %m%n - + + + + - - \ No newline at end of file From a09723f07d15618585036d180c1c11b72d1150a6 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Sun, 8 May 2022 01:09:53 -0700 Subject: [PATCH 020/312] Fix: timer is already cancelled. --- .../managers/StaminaManager/StaminaManager.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index 5065b12b3..5947880e7 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -29,9 +29,7 @@ public class StaminaManager { private Position previousCoordinates = new Position(0, 0, 0); private MotionState currentState = MotionState.MOTION_STANDBY; private MotionState previousState = MotionState.MOTION_STANDBY; - private final Timer sustainedStaminaHandlerTimer = new Timer(); - private final SustainedStaminaHandler handleSustainedStamina = new SustainedStaminaHandler(); - private boolean timerRunning = false; + private Timer sustainedStaminaHandlerTimer; private GameSession cachedSession = null; private GameEntity cachedEntity = null; private int staminaRecoverDelay = 0; @@ -136,21 +134,21 @@ public class StaminaManager { entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); entity.getWorld().broadcastPacket(new PacketLifeStateChangeNotify(0, entity, LifeState.LIFE_DEAD)); player.getScene().removeEntity(entity); - ((EntityAvatar)entity).onDeath(dieType, 0); + ((EntityAvatar) entity).onDeath(dieType, 0); } public void startSustainedStaminaHandler() { - if (!player.isPaused() && !timerRunning) { - timerRunning = true; - sustainedStaminaHandlerTimer.scheduleAtFixedRate(handleSustainedStamina, 0, 200); + if (!player.isPaused() && sustainedStaminaHandlerTimer == null) { + sustainedStaminaHandlerTimer = new Timer(); + sustainedStaminaHandlerTimer.scheduleAtFixedRate(new SustainedStaminaHandler(), 0, 200); // Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer started"); } } public void stopSustainedStaminaHandler() { - if (timerRunning) { - timerRunning = false; + if (sustainedStaminaHandlerTimer != null) { sustainedStaminaHandlerTimer.cancel(); + sustainedStaminaHandlerTimer = null; // Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer stopped"); } } From d78348522e93ac694ef89d9885b3e1c739b29fb7 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Sun, 8 May 2022 04:02:45 -0700 Subject: [PATCH 021/312] Update StaminaManager --- .../AfterUpdateStaminaListener.java | 12 + .../BeforeUpdateStaminaListener.java | 20 ++ .../StaminaManager/ConsumptionType.java | 8 +- .../game/managers/StaminaManager/README.md | 73 +++++ .../StaminaManager/StaminaManager.java | 265 +++++++++++++----- .../recv/HandlerCombatInvocationsNotify.java | 44 +-- 6 files changed, 327 insertions(+), 95 deletions(-) create mode 100644 src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java create mode 100644 src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java create mode 100644 src/main/java/emu/grasscutter/game/managers/StaminaManager/README.md diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java new file mode 100644 index 000000000..bb4f0b188 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java @@ -0,0 +1,12 @@ +package emu.grasscutter.game.managers.StaminaManager; + +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); +} diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java new file mode 100644 index 000000000..02f1f3522 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java @@ -0,0 +1,20 @@ +package emu.grasscutter.game.managers.StaminaManager; + +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); + /** + * 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); +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java index 9a2d8ae24..9afb2171c 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java @@ -10,10 +10,10 @@ public enum ConsumptionType { SPRINT(-1800), DASH(-360), FLY(-60), - SWIM_DASH_START(-200), - SWIM_DASH(-200), - SWIMMING(-80), - FIGHT(0), + SWIM_DASH_START(-20), + SWIM_DASH(-204), + SWIMMING(-80), // TODO: Slow swimming is handled per movement, not per second. Movement frequency depends on gender/age/height. + FIGHT(0), // See StaminaManager.getFightConsumption() // restore STANDBY(500), diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/README.md b/src/main/java/emu/grasscutter/game/managers/StaminaManager/README.md new file mode 100644 index 000000000..39a4e7988 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/README.md @@ -0,0 +1,73 @@ +# Stamina Manager + +--- +## UpdateStamina +```java +// will use consumption.consumptionType as reason +public int updateStaminaRelative(GameSession session, Consumption consumption); +``` +```java +public int updateStaminaAbsolute(GameSession session, String reason, int newStamina) +``` + +--- +## Pause and Resume +```java +public void startSustainedStaminaHandler() +``` +```java +public void stopSustainedStaminaHandler() +``` + + +--- +## Stamina change listeners and intercepting +### BeforeUpdateStaminaListener +```java + +import emu.grasscutter.game.managers.StaminaManager.BeforeUpdateStaminaListener; + +// Listener sample: plugin disable CLIMB_JUMP stamina cost. +private class MyClass implements BeforeUpdateStaminaListener { + // Make your class implement the listener, and pass in your class as a listener. + + public MyClass() { + getStaminaManager().registerBeforeUpdateStaminaListener("myClass", this); + } + + @Override + public boolean onBeforeUpdateStamina(String reason, int newStamina) { + // do not intercept this update + return false; + } + + @Override + public boolean onBeforeUpdateStamina(String reason, Consumption consumption) { + // Try to intercept if this update is CLIMB_JUMP + if (consumption.consumptionType == ConsumptionType.CLIMB_JUMP) { + return true; + } + // If it is not CLIMB_JUMP, do not intercept. + return false; + } +} +``` +### AfterUpdateStaminaListener +```java + +import emu.grasscutter.game.managers.StaminaManager.AfterUpdateStaminaListener; + +// Listener sample: plugin listens for changes already made. +private class MyClass implements AfterUpdateStaminaListener { + // Make your class implement the listener, and pass in your class as a listener. + + public MyClass() { + registerAfterUpdateStaminaListener("myClass", this); + } + + @Override + public void onAfterUpdateStamina(String reason, int newStamina) { + // ... + } +} +``` \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index 5947880e7..72b91c055 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -33,94 +33,170 @@ public class StaminaManager { private GameSession cachedSession = null; private GameEntity cachedEntity = null; private int staminaRecoverDelay = 0; - private boolean isInSkillMove = false; - public boolean getIsInSkillMove() { - return isInSkillMove; - } - public void setIsInSkillMove(boolean b) { - isInSkillMove = b; - } + + private HashMap beforeUpdateStaminaListeners = new HashMap<>(); + private HashMap afterUpdateStaminaListeners = new HashMap<>(); public StaminaManager(Player player) { this.player = player; MotionStatesCategorized.put("SWIM", new HashSet<>(Arrays.asList( - MotionState.MOTION_SWIM_MOVE, - MotionState.MOTION_SWIM_IDLE, - MotionState.MOTION_SWIM_DASH, - MotionState.MOTION_SWIM_JUMP + MotionState.MOTION_SWIM_MOVE, + MotionState.MOTION_SWIM_IDLE, + MotionState.MOTION_SWIM_DASH, + MotionState.MOTION_SWIM_JUMP ))); MotionStatesCategorized.put("STANDBY", new HashSet<>(Arrays.asList( - MotionState.MOTION_STANDBY, - MotionState.MOTION_STANDBY_MOVE, - MotionState.MOTION_DANGER_STANDBY, - MotionState.MOTION_DANGER_STANDBY_MOVE, - MotionState.MOTION_LADDER_TO_STANDBY, - MotionState.MOTION_JUMP_UP_WALL_FOR_STANDBY + MotionState.MOTION_STANDBY, + MotionState.MOTION_STANDBY_MOVE, + MotionState.MOTION_DANGER_STANDBY, + MotionState.MOTION_DANGER_STANDBY_MOVE, + MotionState.MOTION_LADDER_TO_STANDBY, + MotionState.MOTION_JUMP_UP_WALL_FOR_STANDBY ))); MotionStatesCategorized.put("CLIMB", new HashSet<>(Arrays.asList( - MotionState.MOTION_CLIMB, - MotionState.MOTION_CLIMB_JUMP, - MotionState.MOTION_STANDBY_TO_CLIMB, - MotionState.MOTION_LADDER_IDLE, - MotionState.MOTION_LADDER_MOVE, - MotionState.MOTION_LADDER_SLIP, - MotionState.MOTION_STANDBY_TO_LADDER + MotionState.MOTION_CLIMB, + MotionState.MOTION_CLIMB_JUMP, + MotionState.MOTION_STANDBY_TO_CLIMB, + MotionState.MOTION_LADDER_IDLE, + MotionState.MOTION_LADDER_MOVE, + MotionState.MOTION_LADDER_SLIP, + MotionState.MOTION_STANDBY_TO_LADDER ))); MotionStatesCategorized.put("FLY", new HashSet<>(Arrays.asList( - MotionState.MOTION_FLY, - MotionState.MOTION_FLY_IDLE, - MotionState.MOTION_FLY_SLOW, - MotionState.MOTION_FLY_FAST, - MotionState.MOTION_POWERED_FLY + MotionState.MOTION_FLY, + MotionState.MOTION_FLY_IDLE, + MotionState.MOTION_FLY_SLOW, + MotionState.MOTION_FLY_FAST, + MotionState.MOTION_POWERED_FLY ))); MotionStatesCategorized.put("RUN", new HashSet<>(Arrays.asList( - MotionState.MOTION_DASH, - MotionState.MOTION_DANGER_DASH, - MotionState.MOTION_DASH_BEFORE_SHAKE, - MotionState.MOTION_RUN, - MotionState.MOTION_DANGER_RUN, - MotionState.MOTION_WALK, - MotionState.MOTION_DANGER_WALK + MotionState.MOTION_DASH, + MotionState.MOTION_DANGER_DASH, + MotionState.MOTION_DASH_BEFORE_SHAKE, + MotionState.MOTION_RUN, + MotionState.MOTION_DANGER_RUN, + MotionState.MOTION_WALK, + MotionState.MOTION_DANGER_WALK ))); MotionStatesCategorized.put("FIGHT", new HashSet<>(Arrays.asList( - MotionState.MOTION_FIGHT + MotionState.MOTION_FIGHT ))); + + MotionStatesCategorized.put("SKIFF", new HashSet<>(Arrays.asList( + MotionState.MOTION_SKIFF_BOARDING, + MotionState.MOTION_SKIFF_NORMAL, + MotionState.MOTION_SKIFF_DASH, + MotionState.MOTION_SKIFF_POWERED_DASH + ))); + } + + // Listeners + + public boolean registerBeforeUpdateStaminaListener(String listenerName, BeforeUpdateStaminaListener listener) { + if (beforeUpdateStaminaListeners.containsKey(listenerName)) { + return false; + } + beforeUpdateStaminaListeners.put(listenerName, listener); + return true; + } + + public boolean unregisterBeforeUpdateStaminaListener(String listenerName) { + if (!beforeUpdateStaminaListeners.containsKey(listenerName)) { + return false; + } + beforeUpdateStaminaListeners.remove(listenerName); + return true; + } + + public boolean registerAfterUpdateStaminaListener(String listenerName, AfterUpdateStaminaListener listener) { + if (afterUpdateStaminaListeners.containsKey(listenerName)) { + return false; + } + afterUpdateStaminaListeners.put(listenerName, listener); + return true; + } + + public boolean unregisterAfterUpdateStaminaListener(String listenerName) { + if (!afterUpdateStaminaListeners.containsKey(listenerName)) { + return false; + } + afterUpdateStaminaListeners.remove(listenerName); + return true; } private boolean isPlayerMoving() { float diffX = currentCoordinates.getX() - previousCoordinates.getX(); float diffY = currentCoordinates.getY() - previousCoordinates.getY(); float diffZ = currentCoordinates.getZ() - previousCoordinates.getZ(); - Grasscutter.getLogger().debug("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates + + Grasscutter.getLogger().trace("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates + ", " + diffX + ", " + diffY + ", " + diffZ); return Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.2 || Math.abs(diffZ) > 0.3; } - // Returns new stamina and sends PlayerPropNotify - public int updateStamina(GameSession session, Consumption consumption) { + public int updateStaminaRelative(GameSession session, Consumption consumption) { int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); if (consumption.amount == 0) { return currentStamina; } + // notify will update + for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { + Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.consumptionType.toString(), consumption); + if ((overriddenConsumption.consumptionType != consumption.consumptionType) && (overriddenConsumption.amount != consumption.amount)) { + Grasscutter.getLogger().debug("[StaminaManager] Stamina update relative(" + + consumption.consumptionType.toString() + ", " + consumption.amount + ") overridden to relative(" + + consumption.consumptionType.toString() + ", " + consumption.amount + ") by: " + listener.getKey()); + return currentStamina; + } + } int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); - Grasscutter.getLogger().debug(currentStamina + "/" + playerMaxStamina + "\t" + currentState + "\t" + + Grasscutter.getLogger().trace(currentStamina + "/" + playerMaxStamina + "\t" + currentState + "\t" + (isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.consumptionType + "," + consumption.amount + ")"); int newStamina = currentStamina + consumption.amount; if (newStamina < 0) { newStamina = 0; - } - if (newStamina > playerMaxStamina) { + } else if (newStamina > playerMaxStamina) { newStamina = playerMaxStamina; } + return setStamina(session, consumption.consumptionType.toString(), newStamina); + } + + public int updateStaminaAbsolute(GameSession session, String reason, int newStamina) { + int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + // notify will update + for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { + int overriddenNewStamina = listener.getValue().onBeforeUpdateStamina(reason, newStamina); + if (overriddenNewStamina != newStamina) { + Grasscutter.getLogger().debug("[StaminaManager] Stamina update absolute(" + + reason + ", " + newStamina + ") overridden to absolute(" + + reason + ", " + newStamina + ") by: " + listener.getKey()); + return currentStamina; + } + } + int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); + if (newStamina < 0) { + newStamina = 0; + } else if (newStamina > playerMaxStamina) { + newStamina = playerMaxStamina; + } + return setStamina(session, reason, newStamina); + } + + // Returns new stamina and sends PlayerPropNotify + public int setStamina(GameSession session, String reason, int newStamina) { + // set stamina player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); + // notify updated + for (Map.Entry listener : afterUpdateStaminaListeners.entrySet()) { + listener.getValue().onAfterUpdateStamina(reason, newStamina); + } return newStamina; } @@ -141,7 +217,7 @@ public class StaminaManager { if (!player.isPaused() && sustainedStaminaHandlerTimer == null) { sustainedStaminaHandlerTimer = new Timer(); sustainedStaminaHandlerTimer.scheduleAtFixedRate(new SustainedStaminaHandler(), 0, 200); - // Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer started"); + Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer started"); } } @@ -149,7 +225,7 @@ public class StaminaManager { if (sustainedStaminaHandlerTimer != null) { sustainedStaminaHandlerTimer.cancel(); sustainedStaminaHandlerTimer = null; - // Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer stopped"); + Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer stopped"); } } @@ -188,17 +264,17 @@ public class StaminaManager { switch (motionState) { case MOTION_DASH_BEFORE_SHAKE: if (previousState != MotionState.MOTION_DASH_BEFORE_SHAKE) { - updateStamina(session, new Consumption(ConsumptionType.SPRINT)); + updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT)); } break; case MOTION_CLIMB_JUMP: if (previousState != MotionState.MOTION_CLIMB_JUMP) { - updateStamina(session, new Consumption(ConsumptionType.CLIMB_JUMP)); + updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP)); } break; case MOTION_SWIM_DASH: if (previousState != MotionState.MOTION_SWIM_DASH) { - updateStamina(session, new Consumption(ConsumptionType.SWIM_DASH_START)); + updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START)); } break; } @@ -206,7 +282,7 @@ public class StaminaManager { private void handleImmediateStamina(GameSession session, EvtDoSkillSuccNotify notify) { Consumption consumption = getFightConsumption(notify.getSkillId()); - updateStamina(session, consumption); + updateStaminaRelative(session, consumption); } private class SustainedStaminaHandler extends TimerTask { @@ -216,22 +292,30 @@ public class StaminaManager { int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); int maxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); if (moving || (currentStamina < maxStamina)) { - Grasscutter.getLogger().debug("Player moving: " + moving + ", stamina full: " + + Grasscutter.getLogger().trace("Player moving: " + moving + ", stamina full: " + (currentStamina >= maxStamina) + ", recalculate stamina"); + Consumption consumption = new Consumption(ConsumptionType.None); - if (!isInSkillMove) { - if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { - consumption = getClimbSustainedConsumption(); - } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { - consumption = getSwimSustainedConsumptions(); - } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { - consumption = getRunWalkDashSustainedConsumption(); - } else if (MotionStatesCategorized.get("FLY").contains(currentState)) { - consumption = getFlySustainedConsumption(); - } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { - consumption = getStandSustainedConsumption(); - } + if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { + consumption = getClimbSustainedConsumption(); + } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { + consumption = getSwimSustainedConsumptions(); + } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { + consumption = getRunWalkDashSustainedConsumption(); + } else if (MotionStatesCategorized.get("FLY").contains(currentState)) { + consumption = getFlySustainedConsumption(); + } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { + consumption = getStandSustainedConsumption(); } + + /* + TODO: Reductions that apply to all motion types: + Elemental Resonance + Wind: -15% + Skills + Diona E: -10% while shield lasts + Barbara E: -12% while lasts + */ if (cachedSession != null) { if (consumption.amount < 0) { staminaRecoverDelay = 0; @@ -241,12 +325,12 @@ public class StaminaManager { if (staminaRecoverDelay < 10) { // For others recover after 2 seconds (10 ticks) - as official server does. staminaRecoverDelay++; - consumption = new Consumption(ConsumptionType.None); + consumption.amount = 0; + Grasscutter.getLogger().trace("[StaminaManager] Delaying recovery: " + staminaRecoverDelay); } } - updateStamina(cachedSession, consumption); + updateStaminaRelative(cachedSession, consumption); } - handleDrowning(); } } previousState = currentState; @@ -261,10 +345,9 @@ public class StaminaManager { private void handleDrowning() { int stamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); if (stamina < 10) { - boolean isSwimming = MotionStatesCategorized.get("SWIM").contains(currentState); - Grasscutter.getLogger().debug(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + - player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState + "\t" + isSwimming); - if (isSwimming && currentState != MotionState.MOTION_SWIM_IDLE) { + Grasscutter.getLogger().trace(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + + player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState); + if (currentState != MotionState.MOTION_SWIM_IDLE) { killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_DRAWN); } } @@ -272,7 +355,33 @@ public class StaminaManager { // Consumption Calculators + // Stamina Consumption Reduction: https://genshin-impact.fandom.com/wiki/Stamina + private Consumption getFightConsumption(int skillCasting) { + /* TODO: + Instead of handling here, consider call StaminaManager.updateStamina****() with a Consumption object with + type=FIGHT and a modified amount when handling attacks for more accurate attack start/end time and + other info. Handling it here could be very complicated. + Charged attack + Default: + Polearm: (-2500) + Claymore: (-4000 per second, -800 each tick) + Catalyst: (-5000) + Talent: + Ningguang: When Ningguang is in possession of Star Jades, her Charged Attack does not consume Stamina. (Catalyst * 0) + Klee: When Jumpy Dumpty and Normal Attacks deal DMG, Klee has a 50% chance to obtain an Explosive Spark. + This Explosive Spark is consumed by the next Charged Attack, which costs no Stamina. (Catalyst * 0) + Constellations: + Hu Tao: While in a Paramita Papilio state activated by Guide to Afterlife, Hu Tao's Charge Attacks do not consume Stamina. (Polearm * 0) + Character Specific: + Keqing: (-2500) + Diluc: (Claymore * 0.5) + Talent Moving: (Those are skills too) + Ayaka: (-1000 initial) (-1500 per second) When the Cryo application at the end of Kamisato Art: Senho hits an opponent (+1000) + Mona: (-1000 initial) (-1500 per second) + */ + + // TODO: Currently only handling Ayaka and Mona's talent moving initial costs. Consumption consumption = new Consumption(ConsumptionType.None); HashMap fightingCost = new HashMap<>() {{ put(10013, -1000); // Kamisato Ayaka @@ -292,10 +401,12 @@ public class StaminaManager { consumption = new Consumption(ConsumptionType.CLIMB_START); } } + // TODO: Foods return consumption; } private Consumption getSwimSustainedConsumptions() { + handleDrowning(); Consumption consumption = new Consumption(ConsumptionType.None); if (currentState == MotionState.MOTION_SWIM_MOVE) { consumption = new Consumption(ConsumptionType.SWIMMING); @@ -310,6 +421,7 @@ public class StaminaManager { Consumption consumption = new Consumption(ConsumptionType.None); if (currentState == MotionState.MOTION_DASH) { consumption = new Consumption(ConsumptionType.DASH); + // TODO: Foods } if (currentState == MotionState.MOTION_RUN) { consumption = new Consumption(ConsumptionType.RUN); @@ -321,7 +433,12 @@ public class StaminaManager { } private Consumption getFlySustainedConsumption() { + // POWERED_FLY, e.g. wind tunnel + if (currentState == MotionState.MOTION_POWERED_FLY) { + return new Consumption(ConsumptionType.POWERED_FLY); + } Consumption consumption = new Consumption(ConsumptionType.FLY); + // Talent HashMap glidingCostReduction = new HashMap<>() {{ put(212301, 0.8f); // Amber put(222301, 0.8f); // Venti @@ -330,15 +447,15 @@ public class StaminaManager { for (EntityAvatar entity : cachedSession.getPlayer().getTeamManager().getActiveTeam()) { for (int skillId : entity.getAvatar().getProudSkillList()) { if (glidingCostReduction.containsKey(skillId)) { - reduction = glidingCostReduction.get(skillId); + float potentialLowerReduction = glidingCostReduction.get(skillId); + if (potentialLowerReduction < reduction) { + reduction = potentialLowerReduction; + } } } } consumption.amount *= reduction; - // POWERED_FLY, e.g. wind tunnel - if (currentState == MotionState.MOTION_POWERED_FLY) { - consumption = new Consumption(ConsumptionType.POWERED_FLY); - } + // TODO: Foods return consumption; } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java index cc9e7b345..36252f828 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java @@ -49,22 +49,23 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { session.getPlayer().getStaminaManager().handleCombatInvocationsNotify(session, moveInfo, entity); - // TODO: handle MOTION_FIGHT landing - // For plunge attacks, LAND_SPEED is always -30 and is not useful. - // May need the height when starting plunge attack. + // TODO: handle MOTION_FIGHT landing which has a different damage factor + // Also, for plunge attacks, LAND_SPEED is always -30 and is not useful. + // May need the height when starting plunge attack. + // MOTION_LAND_SPEED and MOTION_FALL_ON_GROUND arrive in different packets. + // Cache land speed for later use. + if (motionState == MotionState.MOTION_LAND_SPEED) { + cachedLandingSpeed = motionInfo.getSpeed().getY(); + cachedLandingTimeMillisecond = System.currentTimeMillis(); + monitorLandingEvent = true; + } if (monitorLandingEvent) { if (motionState == MotionState.MOTION_FALL_ON_GROUND) { monitorLandingEvent = false; handleFallOnGround(session, entity, motionState); } } - if (motionState == MotionState.MOTION_LAND_SPEED) { - // MOTION_LAND_SPEED and MOTION_FALL_ON_GROUND arrive in different packet. Cache land speed for later use. - cachedLandingSpeed = motionInfo.getSpeed().getY(); - cachedLandingTimeMillisecond = System.currentTimeMillis(); - monitorLandingEvent = true; - } } break; default: @@ -84,33 +85,42 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { } private void handleFallOnGround(GameSession session, GameEntity entity, MotionState motionState) { - // If not received immediately after MOTION_LAND_SPEED, discard this packet. + // People have reported that after plunge attack (client sends a FIGHT instead of FALL_ON_GROUND) they will die + // if they talk to an NPC (this is when the client sends a FALL_ON_GROUND) without jumping again. + // A dirty patch: if not received immediately after MOTION_LAND_SPEED, discard this packet. + // 200ms seems to be a reasonable delay. int maxDelay = 200; long actualDelay = System.currentTimeMillis() - cachedLandingTimeMillisecond; - Grasscutter.getLogger().debug("MOTION_FALL_ON_GROUND received after " + actualDelay + "/" + maxDelay + "ms." + (actualDelay > maxDelay ? " Discard" : "")); + Grasscutter.getLogger().trace("MOTION_FALL_ON_GROUND received after " + actualDelay + "/" + maxDelay + "ms." + (actualDelay > maxDelay ? " Discard" : "")); if (actualDelay > maxDelay) { return; } float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); - float damage = 0; + float damageFactor = 0; if (cachedLandingSpeed < -23.5) { - damage = (float) (maxHP * 0.33); + damageFactor = 0.33f; } if (cachedLandingSpeed < -25) { - damage = (float) (maxHP * 0.5); + damageFactor = 0.5f; } if (cachedLandingSpeed < -26.5) { - damage = (float) (maxHP * 0.66); + damageFactor = 0.66f; } if (cachedLandingSpeed < -28) { - damage = (maxHP * 1); + damageFactor = 1f; } + float damage = maxHP * damageFactor; float newHP = currentHP - damage; if (newHP < 0) { newHP = 0; } - Grasscutter.getLogger().debug(currentHP + "/" + maxHP + "\t" + "\tDamage: " + damage + "\tnewHP: " + newHP); + if (damageFactor > 0) { + Grasscutter.getLogger().debug(currentHP + "/" + maxHP + "\tLandingSpeed: " + cachedLandingSpeed + + "\tDamageFactor: " + damageFactor + "\tDamage: " + damage + "\tNewHP: " + newHP); + } else { + Grasscutter.getLogger().trace(currentHP + "/" + maxHP + "\tLandingSpeed: 0\tNo damage"); + } entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); if (newHP == 0) { From beea95fdaef5db84868c44d04d728338ec2cfaa4 Mon Sep 17 00:00:00 2001 From: HotaruYS <105128850+HotaruYS@users.noreply.github.com> Date: Sun, 8 May 2022 14:30:48 +0200 Subject: [PATCH 022/312] Improve logging pattern by including caller class name --- src/main/resources/logback.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index bd0740fca..656c1b443 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -3,7 +3,7 @@ - [%d{HH:mm:ss}] [%highlight(%level)] %msg%n + %d{HH:mm:ss} <%highlight(%level):%gray(%class{0})> %msg%n @@ -13,7 +13,7 @@ 24 - %d{yyyy-MM-dd'T'HH:mm:ss'Z'} - %m%n + %d{yyyy-MM-dd'T'HH:mm:ss'Z'} <%level:%class> %m%n @@ -24,4 +24,4 @@ - \ No newline at end of file + From 176f3e91f154ba5fe8b4b79fd68809d419478372 Mon Sep 17 00:00:00 2001 From: Michaellan <67815438+chrisblue@users.noreply.github.com> Date: Sun, 8 May 2022 20:06:10 +0800 Subject: [PATCH 023/312] fill description --- src/main/resources/languages/zh-CN.json | 105 ++++++++++++++++-------- 1 file changed, 71 insertions(+), 34 deletions(-) diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 8faa0e4ae..4e4929aee 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -95,17 +95,20 @@ "create": "已建立账号,UID 为 %s 。", "delete": "账号已刪除。", "no_account": "账号不存在。", - "command_usage": "用法:account [uid]" + "command_usage": "用法:account [uid]", + "description": "创建或删除账号。" }, "broadcast": { "command_usage": "用法:broadcast <消息>", - "message_sent": "公告已发送。" + "message_sent": "公告已发送。", + "description": "向所有玩家发送公告。" }, "changescene": { "usage": "用法:changescene ", "already_in_scene": "你已经在这个秘境中了。", "success": "已切换至秘境 %s.", - "exists_error": "此秘境不存在。" + "exists_error": "此秘境不存在。", + "description": "切换指定秘境。" }, "clear": { "command_usage": "用法: clear ", @@ -115,35 +118,41 @@ "furniture": "已将 %s 的尘歌壶家具清空。", "displays": "已清除 %s 的显示。", "virtuals": "已将 %s 的所有货币和经验值清空。", - "everything": "已将 %s 的所有物品清空。" + "everything": "已将 %s 的所有物品清空。", + "description": "从您的背包中删除所有未装备且已解锁的物品,包括稀有物品。" }, "coop": { "usage": "用法:coop ", - "success": "已强制召唤 %s 到 %s的世界" + "success": "已强制召唤 %s 到 %s的世界", + "description": "强制召唤指定用户到他人的世界。" }, "enter_dungeon": { "usage": "用法:enterdungeon ", "changed": "已进入秘境 %s", "not_found_error": "此秘境不存在。", - "in_dungeon_error": "你已经在秘境中了。" + "in_dungeon_error": "你已经在秘境中了。", + "description": "进入指定秘境。" }, "giveAll": { "usage": "用法:giveall [player] [amount]", "started": "正在给予全部物品...", "success": "已给予全部物品。", - "invalid_amount_or_playerId": "无效的数量/玩家ID。" + "invalid_amount_or_playerId": "无效的数量/玩家ID。", + "description": "给予所有物品。" }, "giveArtifact": { "usage": "用法:giveart|gart [player] [[,]]... [level]", "id_error": "无效的圣遗物ID。", - "success": "已将 %s 给予 %s。" + "success": "已将 %s 给予 %s。", + "description": "给予指定圣遗物。" }, "giveChar": { "usage": "用法:givechar [amount]", "given": "给予角色 %s 等级 %s 向UID %s.", "invalid_avatar_id": "无效的角色ID。", "invalid_avatar_level": "无效的角色等級。.", - "invalid_avatar_or_player_id": "无效的角色ID/玩家ID。" + "invalid_avatar_or_player_id": "无效的角色ID/玩家ID。", + "description": "给予指定角色。" }, "give": { "usage": "用法:give [amount] [level] [refinement]", @@ -151,29 +160,36 @@ "refinement_must_between_1_and_5": "精炼等阶必须在 1 到 5 之间。", "given": "已将 %s 个 %s 给予 %s。", "given_with_level_and_refinement": "已将 %s [等級%s, 精炼%s] %s个给予 %s", - "given_level": "已将 %s 等级 %s %s 个给予UID %s" + "given_level": "已将 %s 等级 %s %s 个给予UID %s", + "description": "给予指定物品。" }, "godmode": { - "success": "上帝模式已被设置为 %s 。 [用户:%s]" + "success": "上帝模式已被设置为 %s 。 [用户:%s]", + "description": "防止你受到伤害。" }, "heal": { - "success": "所有角色已被治疗。" + "success": "所有角色已被治疗。", + "description": "治疗所选队伍的角色。" }, "kick": { "player_kick_player": "玩家 [%s:%s] 已将 [%s:%s] 踢出", - "server_kick_player": "正在踢出玩家 [%s:%s]" + "server_kick_player": "正在踢出玩家 [%s:%s]", + "description": "从服务器内踢出指定玩家。" }, "kill": { "usage": "用法:killall [playerUid] [sceneId]", "scene_not_found_in_player_world": "未在玩家世界中找到此场景", - "kill_monsters_in_scene": "已杀死 %s 个怪物。 [场景ID: %s]" + "kill_monsters_in_scene": "已杀死 %s 个怪物。 [场景ID: %s]", + "description": "杀死所有怪物" }, "killCharacter": { "usage": "用法:/killcharacter [playerId]", - "success": "已杀死 %s 目前使用的角色。" + "success": "已杀死 %s 目前使用的角色。", + "description": "杀死目前使用的角色" }, "list": { - "success": "目前在线人数:%s" + "success": "目前在线人数:%s", + "description": "查看所有玩家" }, "permission": { "usage": "用法:permission ", @@ -181,21 +197,26 @@ "has_error": "此玩家已拥有此权限!", "remove": "权限已移除。", "not_have_error": "此玩家未拥有权限!", - "account_error": "账号不存在!" + "account_error": "账号不存在!", + "description": "给予或移除指定玩家的权限。" }, "position": { - "success": "坐标:%.3f, %.3f, %.3f\n场景ID:%d" + "success": "坐标:%.3f, %.3f, %.3f\n场景ID:%d", + "description": "获取所在位置。" }, "reload": { "reload_start": "正在重载配置文件和数据。", - "reload_done": "重载完毕。" + "reload_done": "重载完毕。", + "description": "重载配置文件和数据。" }, "resetConst": { "reset_all": "重置所有角色的命座。", - "success": "已重置 %s 的命座,重新登录后将会生效。" + "success": "已重置 %s 的命座,重新登录后将会生效。", + "description": "重置当前角色的命之座,执行命令后需重新登录以生效。" }, "resetShopLimit": { - "usage": "用法:/resetshop " + "usage": "用法:/resetshop ", + "description": "重置所选玩家的商店刷新时间。" }, "sendMail": { "usage": "用法:give [player] [amount]", @@ -217,17 +238,20 @@ "message": "<正文>", "sender": "<发件人>", "arguments": " [数量] [等级]", - "error": "错误:无效的编写阶段 %s。需要 StackTrace 请查看服务器控制台。" + "error": "错误:无效的编写阶段 %s。需要 StackTrace 请查看服务器控制台。", + "description": "向指定用户发送邮件。 此命令的用法可根据附加的参数而变化。" }, "sendMessage": { "usage": "用法:sendmessage ", - "success": "消息已发送。" + "success": "消息已发送。", + "description": "向指定玩家发送消息" }, "setFetterLevel": { "usage": "用法:setfetterlevel ", "range_error": "好感度等级必须在 0 到 10 之间。", "fetter_set_level": "好感度已设置为 %s 级", - "level_error": "无效的好感度等级。" + "level_error": "无效的好感度等级。", + "description": "设置当前角色的好感度等级。" }, "setStats": { "usage_console": "用法:setstats|stats @ ", @@ -238,20 +262,24 @@ "player_error": "玩家不存在或已离线。", "set_self": "%s 已经设置为 %s。", "set_for_uid": "%s 的使用者 %s 更改为 %s。", - "set_max_hp": "最大生命值更改为 %s。" + "set_max_hp": "最大生命值更改为 %s。", + "description": "设置当前角色的属性。" }, "setWorldLevel": { "usage": "用法:setworldlevel ", "value_error": "世界等级必须设置在0-8之间。", "success": "已将世界等级设为%s。", - "invalid_world_level": "无效的世界等级。" + "invalid_world_level": "无效的世界等级。", + "description": "设置世界等级,执行命令后需重新登录以生效。" }, "spawn": { "usage": "用法:spawn [amount] [level(仅限怪物]", - "success": "已生成 %s 个 %s。" + "success": "已生成 %s 个 %s。", + "description": "在你附近生成一个生物。" }, "stop": { - "success": "正在关闭服务器..." + "success": "正在关闭服务器...", + "description": "停止服务器" }, "talent": { "usage_1": "设置天赋等级:/talent set ", @@ -267,32 +295,41 @@ "invalid_level": "无效的天赋等级。", "normal_attack_id": "普通攻击的 ID 为 %s。", "e_skill_id": "元素战技ID %s。", - "q_skill_id": "元素爆发ID %s。" + "q_skill_id": "元素爆发ID %s。", + "description": "设置当前角色的天赋等级。" }, "teleportAll": { "success": "已将全部玩家传送到你的位置", - "error": "命令仅限处于多人游戏状态下使用。" + "error": "命令仅限处于多人游戏状态下使用。", + "description": "将你世界中的所有玩家传送到你所在的位置。" }, "teleport": { "usage_server": "用法:/tp @ [scene id]", "usage": "用法:/tp [@] [scene id]", "specify_player_id": "你必须指定一个玩家ID。", "invalid_position": "无效的位置。", - "success": "传送 %s 到坐标 %s,%s,%s,场景为 %s" + "success": "传送 %s 到坐标 %s,%s,%s,场景为 %s", + "description": "改变指定玩家的位置。" }, "weather": { "usage": "用法:weather [climateId]", "success": "已将当前天气设定为 %s,气候为 %s。", - "invalid_id": "无效的天气ID。" + "invalid_id": "无效的天气ID。", + "description": "改变天气" }, "drop": { "command_usage": "用法:drop [amount]", - "success": "已将 %s x %s 丟在附近。" + "success": "已将 %s x %s 丟在附近。", + "description": "在你附近丢一个物品。" }, "help": { "usage": "用法:", "aliases": "別名:", - "available_commands": "可用指令:" + "available_commands": "可用指令:", + "description": "发送帮助信息或显示指定命令的信息。" + }, + "restart": { + "description": "重新启动服务器。" } } } From 897f082b127933767c0d05dc1ecdde4cdea64cca Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Sun, 8 May 2022 04:40:01 -0700 Subject: [PATCH 024/312] Implement AbilityManager --- .../java/emu/grasscutter/data/GameData.java | 7 + .../emu/grasscutter/data/ResourceLoader.java | 69 +++++++ .../data/custom/AbilityModifier.java | 36 ++++ .../data/custom/AbilityModifierEntry.java | 37 ++++ .../game/ability/AbilityManager.java | 185 ++++++++++++++++++ .../grasscutter/game/entity/EntityAvatar.java | 17 ++ .../grasscutter/game/entity/GameEntity.java | 73 +++++++ .../emu/grasscutter/game/player/Player.java | 9 +- .../emu/grasscutter/game/world/Scene.java | 22 +-- .../recv/HandlerAbilityInvocationsNotify.java | 3 +- .../HandlerClientAbilityInitFinishNotify.java | 2 + .../recv/HandlerEvtCreateGadgetNotify.java | 5 - .../recv/HandlerEvtDestroyGadgetNotify.java | 5 - .../HandlerSetEntityClientDataNotify.java | 2 +- 14 files changed, 438 insertions(+), 34 deletions(-) create mode 100644 src/main/java/emu/grasscutter/data/custom/AbilityModifier.java create mode 100644 src/main/java/emu/grasscutter/data/custom/AbilityModifierEntry.java create mode 100644 src/main/java/emu/grasscutter/game/ability/AbilityManager.java diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 76a7f1652..f06d1bda6 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -9,6 +9,8 @@ import java.util.Map; import emu.grasscutter.Grasscutter; import emu.grasscutter.utils.Utils; import emu.grasscutter.data.custom.AbilityEmbryoEntry; +import emu.grasscutter.data.custom.AbilityModifier; +import emu.grasscutter.data.custom.AbilityModifierEntry; import emu.grasscutter.data.custom.OpenConfigEntry; import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.data.def.*; @@ -22,6 +24,7 @@ public class GameData { // BinOutputs private static final Int2ObjectMap abilityHashes = new Int2ObjectOpenHashMap<>(); private static final Map abilityEmbryos = new HashMap<>(); + private static final Map abilityModifiers = new HashMap<>(); private static final Map openConfigEntries = new HashMap<>(); private static final Map scenePointEntries = new HashMap<>(); @@ -101,6 +104,10 @@ public class GameData { return abilityEmbryos; } + public static Map getAbilityModifiers() { + return abilityModifiers; + } + public static Map getOpenConfigEntries() { return openConfigEntries; } diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index b1e3da9ff..c2708bd63 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -18,6 +18,11 @@ import emu.grasscutter.Grasscutter; import emu.grasscutter.data.common.PointData; import emu.grasscutter.data.common.ScenePointConfig; import emu.grasscutter.data.custom.AbilityEmbryoEntry; +import emu.grasscutter.data.custom.AbilityModifier; +import emu.grasscutter.data.custom.AbilityModifier.AbilityConfigData; +import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction; +import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierActionType; +import emu.grasscutter.data.custom.AbilityModifierEntry; import emu.grasscutter.data.custom.OpenConfigEntry; import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.game.world.SpawnDataEntry; @@ -47,6 +52,7 @@ public class ResourceLoader { // Load ability lists loadAbilityEmbryos(); loadOpenConfig(); + loadAbilityModifiers(); // Load resources loadResources(); // Process into depots @@ -244,6 +250,69 @@ public class ResourceLoader { } } + private static void loadAbilityModifiers() { + // Load from BinOutput + File folder = new File(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Ability/Temp/AvatarAbilities/")); + File[] files = folder.listFiles(); + if (files == null) { + Grasscutter.getLogger().error("Error loading ability modifiers: no files found in " + folder.getAbsolutePath()); + return; + } + + for (File file : files) { + List abilityConfigList = null; + + try (FileReader fileReader = new FileReader(file)) { + abilityConfigList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, AbilityConfigData.class).getType()); + } catch (Exception e) { + e.printStackTrace(); + continue; + } + + for (AbilityConfigData data : abilityConfigList) { + if (data.Default.modifiers == null || data.Default.modifiers.size() == 0) { + continue; + } + + AbilityModifierEntry modifierEntry = new AbilityModifierEntry(data.Default.abilityName); + + for (Entry entry : data.Default.modifiers.entrySet()) { + AbilityModifier modifier = entry.getValue(); + + // Stare. + if (modifier.onAdded != null) { + for (AbilityModifierAction action : modifier.onAdded) { + if (action.$type.contains("HealHP")) { + action.type = AbilityModifierActionType.HealHP; + modifierEntry.getOnAdded().add(action); + } + } + } + + if (modifier.onThinkInterval != null) { + for (AbilityModifierAction action : modifier.onThinkInterval) { + if (action.$type.contains("HealHP")) { + action.type = AbilityModifierActionType.HealHP; + modifierEntry.getOnThinkInterval().add(action); + } + } + } + + if (modifier.onRemoved != null) { + for (AbilityModifierAction action : modifier.onRemoved) { + if (action.$type.contains("HealHP")) { + action.type = AbilityModifierActionType.HealHP; + modifierEntry.getOnRemoved().add(action); + } + } + } + } + + GameData.getAbilityModifiers().put(modifierEntry.getName(), modifierEntry); + } + } + } + private static void loadSpawnData() { // Read from cached file if exists File spawnDataEntries = new File(Grasscutter.getConfig().DATA_FOLDER + "Spawns.json"); diff --git a/src/main/java/emu/grasscutter/data/custom/AbilityModifier.java b/src/main/java/emu/grasscutter/data/custom/AbilityModifier.java new file mode 100644 index 000000000..5a1394c65 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/custom/AbilityModifier.java @@ -0,0 +1,36 @@ +package emu.grasscutter.data.custom; + +import java.util.Map; + +public class AbilityModifier { + public AbilityModifierAction[] onAdded; + public AbilityModifierAction[] onThinkInterval; + public AbilityModifierAction[] onRemoved; + + public static class AbilityConfigData { + public AbilityData Default; + } + + public static class AbilityData { + public String abilityName; + public Map modifiers; + } + + public static class AbilityModifierAction { + public String $type; + public AbilityModifierActionType type; + public String target; + public AbilityModifierValue amount; + public AbilityModifierValue amountByTargetCurrentHPRatio; + } + + public static class AbilityModifierValue { + public boolean isFormula; + public boolean isDynamic; + public String dynamicKey; + } + + public enum AbilityModifierActionType { + HealHP, ApplyModifier, LoseHP; + } +} diff --git a/src/main/java/emu/grasscutter/data/custom/AbilityModifierEntry.java b/src/main/java/emu/grasscutter/data/custom/AbilityModifierEntry.java new file mode 100644 index 000000000..b31e0eefe --- /dev/null +++ b/src/main/java/emu/grasscutter/data/custom/AbilityModifierEntry.java @@ -0,0 +1,37 @@ +package emu.grasscutter.data.custom; + +import java.util.ArrayList; +import java.util.List; + +import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction; + +public class AbilityModifierEntry { + private String name; // Custom value + public List onModifierAdded; + public List onThinkInterval; + public List onRemoved; + + public AbilityModifierEntry(String name) { + this.name = name; + this.onModifierAdded = new ArrayList<>(); + this.onThinkInterval = new ArrayList<>(); + this.onRemoved = new ArrayList<>(); + } + + public String getName() { + return name; + } + + public List getOnAdded() { + return onModifierAdded; + } + + public List getOnThinkInterval() { + return onThinkInterval; + } + + public List getOnRemoved() { + return onRemoved; + } + +} diff --git a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java new file mode 100644 index 000000000..72c235b94 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java @@ -0,0 +1,185 @@ +package emu.grasscutter.game.ability; + +import com.google.protobuf.InvalidProtocolBufferException; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.custom.AbilityModifier; +import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction; +import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierActionType; +import emu.grasscutter.data.custom.AbilityModifierEntry; +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.proto.AbilityInvokeArgumentOuterClass.AbilityInvokeArgument; +import emu.grasscutter.net.proto.AbilityInvokeEntryHeadOuterClass.AbilityInvokeEntryHead; +import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; +import emu.grasscutter.net.proto.AbilityMetaModifierChangeOuterClass.AbilityMetaModifierChange; +import emu.grasscutter.net.proto.AbilityMetaReInitOverrideMapOuterClass.AbilityMetaReInitOverrideMap; +import emu.grasscutter.net.proto.AbilityScalarTypeOuterClass.AbilityScalarType; +import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalarValueEntry; +import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction; +import emu.grasscutter.utils.Utils; + +public class AbilityManager { + private Player player; + + public AbilityManager(Player player) { + this.player = player; + } + + public Player getPlayer() { + return this.player; + } + + public void onAbilityInvoke(AbilityInvokeEntry invoke) throws Exception { + //System.out.println(invoke.getArgumentType() + " (" + invoke.getArgumentTypeValue() + "): " + Utils.bytesToHex(invoke.toByteArray())); + switch (invoke.getArgumentType()) { + case ABILITY_META_OVERRIDE_PARAM: + handleOverrideParam(invoke); + break; + case ABILITY_META_REINIT_OVERRIDEMAP: + handleReinitOverrideMap(invoke); + break; + case ABILITY_META_MODIFIER_CHANGE: + handleModifierChange(invoke); + break; + case ABILITY_MIXIN_COST_STAMINA: + handleMixinCostStamina(invoke); + break; + case ABILITY_ACTION_GENERATE_ELEM_BALL: + handleGenerateElemBall(invoke); + break; + default: + break; + } + } + + private void handleOverrideParam(AbilityInvokeEntry invoke) throws Exception { + GameEntity entity = player.getScene().getEntityById(invoke.getEntityId()); + + if (entity == null) { + return; + } + + AbilityScalarValueEntry entry = AbilityScalarValueEntry.parseFrom(invoke.getAbilityData()); + + entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue()); + } + + private void handleReinitOverrideMap(AbilityInvokeEntry invoke) throws Exception { + GameEntity entity = player.getScene().getEntityById(invoke.getEntityId()); + + if (entity == null) { + return; + } + + AbilityMetaReInitOverrideMap map = AbilityMetaReInitOverrideMap.parseFrom(invoke.getAbilityData()); + + for (AbilityScalarValueEntry entry : map.getOverrideMapList()) { + entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue()); + } + } + + private void handleModifierChange(AbilityInvokeEntry invoke) throws Exception { + GameEntity target = player.getScene().getEntityById(invoke.getEntityId()); + if (target == null) { + return; + } + + AbilityInvokeEntryHead head = invoke.getHead(); + if (head == null) { + return; + } + + AbilityMetaModifierChange data = AbilityMetaModifierChange.parseFrom(invoke.getAbilityData()); + if (data == null) { + return; + } + + GameEntity sourceEntity = player.getScene().getEntityById(data.getApplyEntityId()); + if (sourceEntity == null) { + return; + } + + // This is not how it works but we will keep it for now since healing abilities dont work properly anyways + if (data.getAction() == ModifierAction.ADDED && data.getParentAbilityName() != null) { + // Handle add modifier here + String modifierString = data.getParentAbilityName().getStr(); + AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString); + + if (modifier != null && modifier.getOnAdded().size() > 0) { + for (AbilityModifierAction action : modifier.getOnAdded()) { + invokeAction(action, target, sourceEntity); + } + } + + // Add to meta modifier list + target.getMetaModifiers().put(head.getInstancedModifierId(), modifierString); + } else if (data.getAction() == ModifierAction.REMOVED) { + String modifierString = target.getMetaModifiers().get(head.getInstancedModifierId()); + + if (modifierString != null) { + // Get modifier and call on remove event + AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString); + + if (modifier != null && modifier.getOnRemoved().size() > 0) { + for (AbilityModifierAction action : modifier.getOnRemoved()) { + invokeAction(action, target, sourceEntity); + } + } + + // Remove from meta modifiers + target.getMetaModifiers().remove(head.getInstancedModifierId()); + } + } + } + + private void handleMixinCostStamina(AbilityInvokeEntry invoke) { + // Not the right way of doing this + if (Grasscutter.getConfig().OpenStamina) { + // getPlayer().getStaminaManager().updateStamina(getPlayer().getSession(), -450); + // TODO + // set flag in stamina/movement manager that specifies the player is currently using an alternate sprint + } + } + + private void handleGenerateElemBall(AbilityInvokeEntry invoke) { + // TODO create elemental energy orbs + } + + private void invokeAction(AbilityModifierAction action, GameEntity target, GameEntity sourceEntity) { + switch (action.type) { + case HealHP -> { + if (action.amount == null) { + return; + } + + float healAmount = 0; + + if (action.amount.isDynamic && action.amount.dynamicKey != null) { + healAmount = sourceEntity.getMetaOverrideMap().getOrDefault(action.amount.dynamicKey, 0f); + } + + if (healAmount > 0) { + target.heal(healAmount); + } + } + case LoseHP -> { + if (action.amountByTargetCurrentHPRatio == null) { + return; + } + + float damageAmount = 0; + + if (action.amount.isDynamic && action.amount.dynamicKey != null) { + damageAmount = sourceEntity.getMetaOverrideMap().getOrDefault(action.amount.dynamicKey, 0f); + } + + if (damageAmount > 0) { + target.damage(damageAmount); + } + } + } + } +} + diff --git a/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java b/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java index 82efb795f..3c8ef2ba9 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java @@ -17,17 +17,21 @@ import emu.grasscutter.net.proto.AbilityControlBlockOuterClass.AbilityControlBlo import emu.grasscutter.net.proto.AbilityEmbryoOuterClass.AbilityEmbryo; import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; +import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason; 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.FightPropPairOuterClass.FightPropPair; import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; +import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; import emu.grasscutter.net.proto.PropPairOuterClass.PropPair; import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; import emu.grasscutter.net.proto.SceneAvatarInfoOuterClass.SceneAvatarInfo; import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.VectorOuterClass.Vector; +import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify; +import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.utils.Position; import emu.grasscutter.utils.ProtoHelper; import emu.grasscutter.utils.Utils; @@ -110,6 +114,19 @@ public class EntityAvatar extends GameEntity { this.killedBy = killerId; } + @Override + public float heal(float amount) { + float healed = super.heal(amount); + + if (healed > 0f) { + getScene().broadcastPacket( + new PacketEntityFightPropChangeReasonNotify(this, FightProperty.FIGHT_PROP_CUR_HP, healed, PropChangeReason.PROP_CHANGE_ABILITY, ChangeHpReason.ChangeHpAddAbility) + ); + } + + return healed; + } + public SceneAvatarInfo getSceneAvatarInfo() { SceneAvatarInfo.Builder avatarInfo = SceneAvatarInfo.newBuilder() .setUid(this.getPlayer().getUid()) diff --git a/src/main/java/emu/grasscutter/game/entity/GameEntity.java b/src/main/java/emu/grasscutter/game/entity/GameEntity.java index 627b41103..0cdcc3ebd 100644 --- a/src/main/java/emu/grasscutter/game/entity/GameEntity.java +++ b/src/main/java/emu/grasscutter/game/entity/GameEntity.java @@ -1,5 +1,8 @@ package emu.grasscutter.game.entity; +import java.util.HashMap; +import java.util.Map; + import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.LifeState; import emu.grasscutter.game.world.Scene; @@ -9,8 +12,11 @@ import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.VectorOuterClass.Vector; +import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.utils.Position; import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; public abstract class GameEntity { protected int id; @@ -25,6 +31,10 @@ public abstract class GameEntity { private int lastMoveSceneTimeMs; private int lastMoveReliableSeq; + // Abilities + private Map metaOverrideMap; + private Int2ObjectMap metaModifiers; + public GameEntity(Scene scene) { this.scene = scene; this.moveState = MotionState.MOTION_NONE; @@ -54,6 +64,20 @@ public abstract class GameEntity { return isAlive() ? LifeState.LIFE_ALIVE : LifeState.LIFE_DEAD; } + public Map getMetaOverrideMap() { + if (this.metaOverrideMap == null) { + this.metaOverrideMap = new HashMap<>(); + } + return this.metaOverrideMap; + } + + public Int2ObjectMap getMetaModifiers() { + if (this.metaModifiers == null) { + this.metaModifiers = new Int2ObjectOpenHashMap<>(); + } + return this.metaModifiers; + } + public abstract Int2FloatOpenHashMap getFightProperties(); public abstract Position getPosition(); @@ -146,4 +170,53 @@ public abstract class GameEntity { public void setSpawnEntry(SpawnDataEntry spawnEntry) { this.spawnEntry = spawnEntry; } + + public float heal(float amount) { + if (this.getFightProperties() == null) { + return 0f; + } + + float curHp = getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + float maxHp = getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + + if (curHp >= maxHp) { + return 0f; + } + + float healed = Math.min(maxHp - curHp, amount); + this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, healed); + + getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP)); + + return healed; + } + + public void damage(float amount) { + damage(amount, 0); + } + + public void damage(float amount, int killerId) { + // Sanity check + if (getFightProperties() == null) { + return; + } + + // Lose hp + addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -amount); + + // Check if dead + boolean isDead = false; + if (getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) { + setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f); + isDead = true; + } + + // Packets + this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP)); + + // Check if dead + if (isDead) { + getScene().killEntity(this, 0); + } + } } diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 71ae9d8c6..477f974ea 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -8,6 +8,7 @@ import emu.grasscutter.data.def.PlayerLevelData; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.Account; import emu.grasscutter.game.CoopRequest; +import emu.grasscutter.game.ability.AbilityManager; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.AvatarProfileData; import emu.grasscutter.game.avatar.AvatarStorage; @@ -89,7 +90,8 @@ public class Player { @Transient private FriendsList friendsList; @Transient private MailHandler mailHandler; @Transient private MessageHandler messageHandler; - + @Transient private AbilityManager abilityManager; + @Transient private SotSManager sotsManager; private TeamManager teamManager; @@ -142,6 +144,7 @@ public class Player { this.friendsList = new FriendsList(this); this.mailHandler = new MailHandler(this); this.towerManager = new TowerManager(this); + this.abilityManager = new AbilityManager(this); this.pos = new Position(); this.rotation = new Position(); this.properties = new HashMap<>(); @@ -1025,6 +1028,10 @@ public class Player { public SotSManager getSotSManager() { return sotsManager; } + public AbilityManager getAbilityManager() { + return abilityManager; + } + public synchronized void onTick() { // Check ping if (this.getLastPingTime() > System.currentTimeMillis() + 60000) { diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 82ce9139f..daed26e3e 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -385,27 +385,7 @@ public class Scene { } // Sanity check - if (target.getFightProperties() == null) { - return; - } - - // Lose hp - target.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -result.getDamage()); - - // Check if dead - boolean isDead = false; - if (target.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) { - target.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f); - isDead = true; - } - - // Packets - this.broadcastPacket(new PacketEntityFightPropUpdateNotify(target, FightProperty.FIGHT_PROP_CUR_HP)); - - // Check if dead - if (isDead) { - this.killEntity(target, result.getAttackerId()); - } + target.damage(result.getDamage(), result.getAttackerId()); } public void killEntity(GameEntity target, int attackerId) { diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAbilityInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAbilityInvocationsNotify.java index 710ea0fea..8be2d1c1f 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAbilityInvocationsNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAbilityInvocationsNotify.java @@ -6,6 +6,7 @@ import emu.grasscutter.net.proto.AbilityInvocationsNotifyOuterClass.AbilityInvoc import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.utils.Utils; @Opcodes(PacketOpcodes.AbilityInvocationsNotify) public class HandlerAbilityInvocationsNotify extends PacketHandler { @@ -15,7 +16,7 @@ public class HandlerAbilityInvocationsNotify extends PacketHandler { AbilityInvocationsNotify notif = AbilityInvocationsNotify.parseFrom(payload); for (AbilityInvokeEntry entry : notif.getInvokesList()) { - //System.out.println(entry.getArgumentType() + ": " + Utils.bytesToHex(entry.getAbilityData().toByteArray())); + session.getPlayer().getAbilityManager().onAbilityInvoke(entry); session.getPlayer().getAbilityInvokeHandler().addEntry(entry.getForwardType(), entry); } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerClientAbilityInitFinishNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerClientAbilityInitFinishNotify.java index cfe697b91..a1035af85 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerClientAbilityInitFinishNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerClientAbilityInitFinishNotify.java @@ -6,6 +6,7 @@ import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry import emu.grasscutter.net.proto.ClientAbilityInitFinishNotifyOuterClass.ClientAbilityInitFinishNotify; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.utils.Utils; @Opcodes(PacketOpcodes.ClientAbilityInitFinishNotify) public class HandlerClientAbilityInitFinishNotify extends PacketHandler { @@ -15,6 +16,7 @@ public class HandlerClientAbilityInitFinishNotify extends PacketHandler { ClientAbilityInitFinishNotify notif = ClientAbilityInitFinishNotify.parseFrom(payload); for (AbilityInvokeEntry entry : notif.getInvokesList()) { + session.getPlayer().getAbilityManager().onAbilityInvoke(entry); session.getPlayer().getClientAbilityInitFinishHandler().addEntry(entry.getForwardType(), entry); } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtCreateGadgetNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtCreateGadgetNotify.java index 9b1cdb0fb..92229d400 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtCreateGadgetNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtCreateGadgetNotify.java @@ -14,11 +14,6 @@ public class HandlerEvtCreateGadgetNotify extends PacketHandler { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { EvtCreateGadgetNotify notify = EvtCreateGadgetNotify.parseFrom(payload); - // Dont handle in singleplayer - if (!session.getPlayer().getWorld().isMultiplayer()) { - return; - } - // Sanity check - dont add duplicate entities if (session.getPlayer().getScene().getEntityById(notify.getEntityId()) != null) { return; diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDestroyGadgetNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDestroyGadgetNotify.java index 608215d0a..7d1abe8ea 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDestroyGadgetNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDestroyGadgetNotify.java @@ -12,11 +12,6 @@ public class HandlerEvtDestroyGadgetNotify extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { EvtDestroyGadgetNotify notify = EvtDestroyGadgetNotify.parseFrom(payload); - - // Dont handle in singleplayer - if (!session.getPlayer().getWorld().isMultiplayer()) { - return; - } session.getPlayer().getScene().onPlayerDestroyGadget(notify.getEntityId()); } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetEntityClientDataNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetEntityClientDataNotify.java index 5151034f2..6c4d86f7e 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetEntityClientDataNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetEntityClientDataNotify.java @@ -23,7 +23,7 @@ public class HandlerSetEntityClientDataNotify extends PacketHandler { BasePacket packet = new BasePacket(PacketOpcodes.SetEntityClientDataNotify, true); packet.setData(notif); - session.getPlayer().getScene().broadcastPacketToOthers(session.getPlayer(), packet); + session.getPlayer().getScene().broadcastPacket(packet); } } From 2dfdc62743dd1dc2e94fee1b2e2ffa1131663d57 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Sun, 8 May 2022 05:31:53 -0700 Subject: [PATCH 025/312] Implement energy balls (orbs) --- .../game/ability/AbilityManager.java | 21 +++++++++- .../emu/grasscutter/game/avatar/Avatar.java | 26 ++++++++---- .../game/avatar/AvatarStorage.java | 5 ++- .../grasscutter/game/entity/EntityAvatar.java | 19 ++++++++- .../grasscutter/game/inventory/Inventory.java | 3 ++ .../grasscutter/game/player/TeamManager.java | 19 +++++++++ .../grasscutter/game/props/ElementType.java | 42 +++++++++++-------- .../recv/HandlerSetPlayerBornDataReq.java | 2 +- ...cketEntityFightPropChangeReasonNotify.java | 22 +++++++++- 9 files changed, 128 insertions(+), 31 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java index 72c235b94..d1ae388ea 100644 --- a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java +++ b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java @@ -7,9 +7,12 @@ import emu.grasscutter.data.GameData; import emu.grasscutter.data.custom.AbilityModifier; import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction; import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierActionType; +import emu.grasscutter.data.def.ItemData; import emu.grasscutter.data.custom.AbilityModifierEntry; +import emu.grasscutter.game.entity.EntityItem; import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.AbilityActionGenerateElemBall; import emu.grasscutter.net.proto.AbilityInvokeArgumentOuterClass.AbilityInvokeArgument; import emu.grasscutter.net.proto.AbilityInvokeEntryHeadOuterClass.AbilityInvokeEntryHead; import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; @@ -18,6 +21,7 @@ import emu.grasscutter.net.proto.AbilityMetaReInitOverrideMapOuterClass.AbilityM import emu.grasscutter.net.proto.AbilityScalarTypeOuterClass.AbilityScalarType; import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalarValueEntry; import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction; +import emu.grasscutter.utils.Position; import emu.grasscutter.utils.Utils; public class AbilityManager { @@ -143,8 +147,21 @@ public class AbilityManager { } } - private void handleGenerateElemBall(AbilityInvokeEntry invoke) { - // TODO create elemental energy orbs + private void handleGenerateElemBall(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException { + AbilityActionGenerateElemBall action = AbilityActionGenerateElemBall.parseFrom(invoke.getAbilityData()); + if (action == null) { + return; + } + + ItemData itemData = GameData.getItemDataMap().get(2024); + if (itemData == null) { + return; // Should never happen + } + + EntityItem energyBall = new EntityItem(getPlayer().getScene(), getPlayer(), itemData, new Position(action.getPos()), 1); + energyBall.getRotation().set(action.getRot()); + + getPlayer().getScene().addEntity(energyBall); } private void invokeAction(AbilityModifierAction action, GameEntity target, GameEntity sourceEntity) { diff --git a/src/main/java/emu/grasscutter/game/avatar/Avatar.java b/src/main/java/emu/grasscutter/game/avatar/Avatar.java index b0bfb0801..c4fb85671 100644 --- a/src/main/java/emu/grasscutter/game/avatar/Avatar.java +++ b/src/main/java/emu/grasscutter/game/avatar/Avatar.java @@ -69,6 +69,7 @@ public class Avatar { @Transient private Player owner; @Transient private AvatarData data; + @Transient private AvatarSkillDepotData skillDepot; @Transient private long guid; // Player unique id private int avatarId; // Id of avatar @@ -103,8 +104,8 @@ public class Avatar { private int nameCardRewardId; private int nameCardId; + @Deprecated // Do not use. Morhpia only! public Avatar() { - // Morhpia only! this.equips = new Int2ObjectOpenHashMap<>(); this.fightProp = new Int2FloatOpenHashMap(); this.extraAbilityEmbryos = new HashSet<>(); @@ -140,7 +141,7 @@ public class Avatar { } // Skill depot - this.setSkillDepot(getAvatarData().getSkillDepot()); + this.setSkillDepotData(getAvatarData().getSkillDepot()); // Set stats this.recalcStats(); @@ -164,7 +165,8 @@ public class Avatar { } protected void setAvatarData(AvatarData data) { - this.data = data; + if (this.data != null) return; + this.data = data; // Used while loading this from the database } public int getOwnerId() { @@ -257,9 +259,19 @@ public class Avatar { return skillDepotId; } - public void setSkillDepot(AvatarSkillDepotData skillDepot) { - // Set id + public AvatarSkillDepotData getSkillDepot() { + return skillDepot; + } + + protected void setSkillDepot(AvatarSkillDepotData skillDepot) { + if (this.skillDepot != null) return; + this.skillDepot = skillDepot; // Used while loading this from the database + } + + public void setSkillDepotData(AvatarSkillDepotData skillDepot) { + // Set id and depot this.skillDepotId = skillDepot.getId(); + this.skillDepot = skillDepot; // Clear, then add skills getSkillLevelMap().clear(); if (skillDepot.getEnergySkill() > 0) { @@ -501,8 +513,8 @@ public class Avatar { // Set energy usage if (data.getSkillDepot() != null && data.getSkillDepot().getEnergySkillData() != null) { ElementType element = data.getSkillDepot().getElementType(); - this.setFightProperty(element.getEnergyProperty(), data.getSkillDepot().getEnergySkillData().getCostElemVal()); - this.setFightProperty((element.getEnergyProperty().getId() % 70) + 1000, data.getSkillDepot().getEnergySkillData().getCostElemVal()); + this.setFightProperty(element.getMaxEnergyProp(), data.getSkillDepot().getEnergySkillData().getCostElemVal()); + this.setFightProperty((element.getMaxEnergyProp().getId() % 70) + 1000, data.getSkillDepot().getEnergySkillData().getCostElemVal()); } // Artifacts diff --git a/src/main/java/emu/grasscutter/game/avatar/AvatarStorage.java b/src/main/java/emu/grasscutter/game/avatar/AvatarStorage.java index 2486e36ab..de3221473 100644 --- a/src/main/java/emu/grasscutter/game/avatar/AvatarStorage.java +++ b/src/main/java/emu/grasscutter/game/avatar/AvatarStorage.java @@ -5,6 +5,7 @@ import java.util.List; import emu.grasscutter.data.GameData; import emu.grasscutter.data.def.AvatarData; +import emu.grasscutter.data.def.AvatarSkillDepotData; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.inventory.GameItem; @@ -139,12 +140,14 @@ public class AvatarStorage implements Iterable { } AvatarData avatarData = GameData.getAvatarDataMap().get(avatar.getAvatarId()); - if (avatarData == null) { + AvatarSkillDepotData skillDepot = GameData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId()); + if (avatarData == null || skillDepot == null) { continue; } // Set ownerships avatar.setAvatarData(avatarData); + avatar.setSkillDepot(skillDepot); avatar.setOwner(getPlayer()); // Force recalc of const boosted skills diff --git a/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java b/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java index 3c8ef2ba9..858db3de2 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java @@ -30,6 +30,7 @@ import emu.grasscutter.net.proto.SceneAvatarInfoOuterClass.SceneAvatarInfo; import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.VectorOuterClass.Vector; +import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.utils.Position; @@ -127,6 +128,22 @@ public class EntityAvatar extends GameEntity { return healed; } + public void addEnergy(float amount) { + FightProperty curEnergyProp = getAvatar().getSkillDepot().getElementType().getCurEnergyProp(); + FightProperty maxEnergyProp = getAvatar().getSkillDepot().getElementType().getMaxEnergyProp(); + + float curEnergy = this.getFightProperty(curEnergyProp); + float maxEnergy = this.getFightProperty(maxEnergyProp); + float newEnergy = Math.min(curEnergy + amount, maxEnergy); + + if (newEnergy != curEnergy) { + setFightProperty(curEnergyProp, newEnergy); + + getScene().broadcastPacket(new PacketAvatarFightPropUpdateNotify(getAvatar(), curEnergyProp)); + getScene().broadcastPacket(new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, newEnergy, PropChangeReason.PROP_CHANGE_ENERGY_BALL)); + } + } + public SceneAvatarInfo getSceneAvatarInfo() { SceneAvatarInfo.Builder avatarInfo = SceneAvatarInfo.newBuilder() .setUid(this.getPlayer().getUid()) @@ -258,5 +275,5 @@ public class EntityAvatar extends GameEntity { // return abilityControlBlock.build(); - } + } } diff --git a/src/main/java/emu/grasscutter/game/inventory/Inventory.java b/src/main/java/emu/grasscutter/game/inventory/Inventory.java index 14d1ae203..c4158ee6f 100644 --- a/src/main/java/emu/grasscutter/game/inventory/Inventory.java +++ b/src/main/java/emu/grasscutter/game/inventory/Inventory.java @@ -172,6 +172,9 @@ public class Inventory implements Iterable { // Handle this.addVirtualItem(item.getItemId(), item.getCount()); return item; + } else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_ADSORBATE) { + player.getTeamManager().addEnergyToTeam(item); + return null; } else if (item.getItemData().getMaterialType() == MaterialType.MATERIAL_AVATAR) { // Get avatar id int avatarId = (item.getItemId() % 1000) + 10000000; diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index 204af2976..891d0a215 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -10,6 +10,7 @@ import emu.grasscutter.data.def.AvatarSkillDepotData; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.EntityBaseGadget; +import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.props.ElementType; import emu.grasscutter.game.props.EnterReason; import emu.grasscutter.game.props.FightProperty; @@ -579,6 +580,24 @@ public class TeamManager { // Packets getPlayer().sendPacket(new BasePacket(PacketOpcodes.WorldPlayerReviveRsp)); } + + public synchronized void addEnergyToTeam(GameItem energyBall) { + // TODO + float baseEnergy = 2; + + for (int i = 0; i < getActiveTeam().size(); i++) { + EntityAvatar entity = getActiveTeam().get(i); + + float energyGain = baseEnergy; + + // Active character gets full hp + if (getCurrentCharacterIndex() != i) { + energyGain *= Math.max(1.0 - (getActiveTeam().size() * .1f), .6f); + } + + entity.addEnergy(energyGain); + } + } public void saveAvatars() { // Save all avatars from active team diff --git a/src/main/java/emu/grasscutter/game/props/ElementType.java b/src/main/java/emu/grasscutter/game/props/ElementType.java index 23362c39f..12a30f6fc 100644 --- a/src/main/java/emu/grasscutter/game/props/ElementType.java +++ b/src/main/java/emu/grasscutter/game/props/ElementType.java @@ -9,21 +9,22 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; public enum ElementType { - None (0, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY), - Fire (1, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, 10101, "TeamResonance_Fire_Lv2"), - Water (2, FightProperty.FIGHT_PROP_MAX_WATER_ENERGY, 10201, "TeamResonance_Water_Lv2"), - Grass (3, FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY), - Electric (4, FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY, 10401, "TeamResonance_Electric_Lv2"), - Ice (5, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY, 10601, "TeamResonance_Ice_Lv2"), - Frozen (6, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY), - Wind (7, FightProperty.FIGHT_PROP_MAX_WIND_ENERGY, 10301, "TeamResonance_Wind_Lv2"), - Rock (8, FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY, 10701, "TeamResonance_Rock_Lv2"), - AntiFire (9, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY), - Default (255, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, 10801, "TeamResonance_AllDifferent"); + None (0, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY), + Fire (1, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, 10101, "TeamResonance_Fire_Lv2"), + Water (2, FightProperty.FIGHT_PROP_CUR_WATER_ENERGY, FightProperty.FIGHT_PROP_MAX_WATER_ENERGY, 10201, "TeamResonance_Water_Lv2"), + Grass (3, FightProperty.FIGHT_PROP_CUR_GRASS_ENERGY, FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY), + Electric (4, FightProperty.FIGHT_PROP_CUR_ELEC_ENERGY, FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY, 10401, "TeamResonance_Electric_Lv2"), + Ice (5, FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY, 10601, "TeamResonance_Ice_Lv2"), + Frozen (6, FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY), + Wind (7, FightProperty.FIGHT_PROP_CUR_WIND_ENERGY, FightProperty.FIGHT_PROP_MAX_WIND_ENERGY, 10301, "TeamResonance_Wind_Lv2"), + Rock (8, FightProperty.FIGHT_PROP_CUR_ROCK_ENERGY, FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY, 10701, "TeamResonance_Rock_Lv2"), + AntiFire (9, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY), + Default (255, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY, 10801, "TeamResonance_AllDifferent"); private final int value; private final int teamResonanceId; - private final FightProperty energyProperty; + private final FightProperty curEnergyProp; + private final FightProperty maxEnergyProp; private final int configHash; private static final Int2ObjectMap map = new Int2ObjectOpenHashMap<>(); private static final Map stringMap = new HashMap<>(); @@ -35,13 +36,14 @@ public enum ElementType { }); } - private ElementType(int value, FightProperty energyProperty) { - this(value, energyProperty, 0, null); + private ElementType(int value, FightProperty curEnergyProp, FightProperty maxEnergyProp) { + this(value, curEnergyProp, maxEnergyProp, 0, null); } - private ElementType(int value, FightProperty energyProperty, int teamResonanceId, String configName) { + private ElementType(int value, FightProperty curEnergyProp, FightProperty maxEnergyProp, int teamResonanceId, String configName) { this.value = value; - this.energyProperty = energyProperty; + this.curEnergyProp = curEnergyProp; + this.maxEnergyProp = maxEnergyProp; this.teamResonanceId = teamResonanceId; if (configName != null) { this.configHash = Utils.abilityHash(configName); @@ -54,8 +56,12 @@ public enum ElementType { return value; } - public FightProperty getEnergyProperty() { - return energyProperty; + public FightProperty getCurEnergyProp() { + return curEnergyProp; + } + + public FightProperty getMaxEnergyProp() { + return maxEnergyProp; } public int getTeamResonanceId() { diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java index 2487df063..53d141a99 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java @@ -62,7 +62,7 @@ public class HandlerSetPlayerBornDataReq extends PacketHandler { // Create avatar if (player.getAvatars().getAvatarCount() == 0) { Avatar mainCharacter = new Avatar(avatarId); - mainCharacter.setSkillDepot(GameData.getAvatarSkillDepotDataMap().get(startingSkillDepot)); + mainCharacter.setSkillDepotData(GameData.getAvatarSkillDepotDataMap().get(startingSkillDepot)); player.addAvatar(mainCharacter); player.setMainCharacterId(avatarId); player.setHeadImage(avatarId); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketEntityFightPropChangeReasonNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketEntityFightPropChangeReasonNotify.java index 5778f711a..366354a40 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketEntityFightPropChangeReasonNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketEntityFightPropChangeReasonNotify.java @@ -11,21 +11,27 @@ import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; import java.util.List; public class PacketEntityFightPropChangeReasonNotify extends BasePacket { + public PacketEntityFightPropChangeReasonNotify(GameEntity entity, FightProperty prop, Float value, List param, PropChangeReason reason, ChangeHpReason changeHpReason) { super(PacketOpcodes.EntityFightPropChangeReasonNotify); + EntityFightPropChangeReasonNotify.Builder proto = EntityFightPropChangeReasonNotify.newBuilder() .setEntityId(entity.getId()) .setPropType(prop.getId()) .setPropDelta(value) .setReason(reason) .setChangeHpReason(changeHpReason); - for(int p: param){ + + for(int p : param){ proto.addParamList(p); } + this.setData(proto); } + public PacketEntityFightPropChangeReasonNotify(GameEntity entity, FightProperty prop, Float value, PropChangeReason reason, ChangeHpReason changeHpReason) { super(PacketOpcodes.EntityFightPropChangeReasonNotify); + EntityFightPropChangeReasonNotify proto = EntityFightPropChangeReasonNotify.newBuilder() .setEntityId(entity.getId()) .setPropType(prop.getId()) @@ -33,6 +39,20 @@ public class PacketEntityFightPropChangeReasonNotify extends BasePacket { .setReason(reason) .setChangeHpReason(changeHpReason) .build(); + + this.setData(proto); + } + + public PacketEntityFightPropChangeReasonNotify(GameEntity entity, FightProperty prop, Float value, PropChangeReason reason) { + super(PacketOpcodes.EntityFightPropChangeReasonNotify); + + EntityFightPropChangeReasonNotify proto = EntityFightPropChangeReasonNotify.newBuilder() + .setEntityId(entity.getId()) + .setPropType(prop.getId()) + .setPropDelta(value) + .setReason(reason) + .build(); + this.setData(proto); } } From 55389d3a5cd064f6ad23ebde7278bbdac3ea4c2c Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Sun, 8 May 2022 05:39:12 -0700 Subject: [PATCH 026/312] Optimize invoke packet handling --- .../java/emu/grasscutter/game/player/InvokeHandler.java | 2 +- .../packet/recv/HandlerAbilityInvocationsNotify.java | 4 ---- .../packet/recv/HandlerCombatInvocationsNotify.java | 8 -------- .../server/packet/recv/HandlerUnionCmdNotify.java | 9 +++++++++ 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/player/InvokeHandler.java b/src/main/java/emu/grasscutter/game/player/InvokeHandler.java index edfcbfc83..b8a9ed89f 100644 --- a/src/main/java/emu/grasscutter/game/player/InvokeHandler.java +++ b/src/main/java/emu/grasscutter/game/player/InvokeHandler.java @@ -30,7 +30,7 @@ public class InvokeHandler { } public synchronized void update(Player player) { - if (player.getWorld() == null) { + if (player.getWorld() == null || player.getScene() == null) { this.entryListForwardAll.clear(); this.entryListForwardAllExceptCur.clear(); this.entryListForwardHost.clear(); diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAbilityInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAbilityInvocationsNotify.java index 8be2d1c1f..a5d4c7f36 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAbilityInvocationsNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAbilityInvocationsNotify.java @@ -19,10 +19,6 @@ public class HandlerAbilityInvocationsNotify extends PacketHandler { session.getPlayer().getAbilityManager().onAbilityInvoke(entry); session.getPlayer().getAbilityInvokeHandler().addEntry(entry.getForwardType(), entry); } - - if (notif.getInvokesList().size() > 0) { - session.getPlayer().getAbilityInvokeHandler().update(session.getPlayer()); - } } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java index 36252f828..50fca5101 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java @@ -74,14 +74,6 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { session.getPlayer().getCombatInvokeHandler().addEntry(entry.getForwardType(), entry); } - - if (notif.getInvokeListList().size() > 0) { - session.getPlayer().getCombatInvokeHandler().update(session.getPlayer()); - } - // Handle attack results last - while (!session.getPlayer().getAttackResults().isEmpty()) { - session.getPlayer().getScene().handleAttack(session.getPlayer().getAttackResults().poll()); - } } private void handleFallOnGround(GameSession session, GameEntity entity, MotionState motionState) { diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerUnionCmdNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerUnionCmdNotify.java index 1f4a9e7f3..2468f675f 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerUnionCmdNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerUnionCmdNotify.java @@ -15,5 +15,14 @@ public class HandlerUnionCmdNotify extends PacketHandler { for (UnionCmd cmd : req.getCmdListList()) { session.getServer().getPacketHandler().handle(session, cmd.getMessageId(), EMPTY_BYTE_ARRAY, cmd.getBody().toByteArray()); } + + // Update + session.getPlayer().getCombatInvokeHandler().update(session.getPlayer()); + session.getPlayer().getAbilityInvokeHandler().update(session.getPlayer()); + + // Handle attack results last + while (!session.getPlayer().getAttackResults().isEmpty()) { + session.getPlayer().getScene().handleAttack(session.getPlayer().getAttackResults().poll()); + } } } From 9ad44f5c10abce4be6a6df7f11d619ac4a24d6f3 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Sat, 7 May 2022 21:47:13 +0800 Subject: [PATCH 027/312] Monsters tide turn by turn && Ban User Skill && Lua functions --- src/main/resources/logback.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index bd0740fca..5ab1957e5 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -24,4 +24,6 @@ + + \ No newline at end of file From 9fc4b916c8be1a1cb0183e8d0b7a8cebbba8552d Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Sun, 8 May 2022 17:11:02 +0800 Subject: [PATCH 028/312] Persist Tower Data && Set The Tower Schedule --- build.gradle | 2 +- data/TowerSchedule.json | 5 ++ .../command/commands/UnlockTowerCommand.java | 32 +++++++ .../java/emu/grasscutter/data/GameData.java | 4 + .../data/def/TowerScheduleData.java | 70 ++++++++++++++++ .../dungeons/TowerDungeonSettleListener.java | 16 ++-- .../game/tower/TowerLevelRecord.java | 64 ++++++++++++++ .../grasscutter/game/tower/TowerManager.java | 83 +++++++++++++++---- .../game/tower/TowerScheduleConfig.java | 35 ++++++++ .../game/tower/TowerScheduleManager.java | 75 +++++++++++++++++ .../scripts/SceneScriptManager.java | 29 ++++--- .../emu/grasscutter/scripts/ScriptLib.java | 32 ++++--- .../grasscutter/server/game/GameServer.java | 8 +- .../packet/recv/HandlerTowerAllDataReq.java | 5 +- .../send/PacketDungeonSettleNotify.java | 2 +- .../packet/send/PacketTowerAllDataRsp.java | 57 +++++++++---- .../PacketTowerFloorRecordChangeNotify.java | 6 +- .../send/PacketTowerLevelStarCondNotify.java | 32 +++++++ .../emu/grasscutter/utils/DateHelper.java | 6 +- src/main/resources/languages/en-US.json | 3 + 20 files changed, 500 insertions(+), 66 deletions(-) create mode 100644 data/TowerSchedule.json create mode 100644 src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java create mode 100644 src/main/java/emu/grasscutter/data/def/TowerScheduleData.java create mode 100644 src/main/java/emu/grasscutter/game/tower/TowerLevelRecord.java create mode 100644 src/main/java/emu/grasscutter/game/tower/TowerScheduleConfig.java create mode 100644 src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketTowerLevelStarCondNotify.java diff --git a/build.gradle b/build.gradle index eefef5b48..dd8fa9fa0 100644 --- a/build.gradle +++ b/build.gradle @@ -70,7 +70,7 @@ dependencies { implementation group: 'io.netty', name: 'netty-all', version: '4.1.71.Final' - implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.8' + implementation group: 'com.google.code.gson', name: 'gson', version: '2.9.0' implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '3.18.2' implementation group: 'org.reflections', name: 'reflections', version: '0.10.2' diff --git a/data/TowerSchedule.json b/data/TowerSchedule.json new file mode 100644 index 000000000..b93100645 --- /dev/null +++ b/data/TowerSchedule.json @@ -0,0 +1,5 @@ +{ + "scheduleId" : 1, + "scheduleStartTime" : "2022-05-01T00:00:00+08:00", + "nextScheduleChangeTime" : "2022-05-30T00:00:00+08:00" +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java b/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java new file mode 100644 index 000000000..e0fce695c --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java @@ -0,0 +1,32 @@ +package emu.grasscutter.command.commands; + +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.tower.TowerLevelRecord; + +import java.util.List; + +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "unlocktower", usage = "unlocktower", aliases = {"ut"}, + description = "Unlock all levels of tower", permission = "player.tower") +public class UnlockTowerCommand implements CommandHandler { + + @Override + public void execute(Player sender, Player targetPlayer, List args) { + unlockFloor(sender, sender.getServer().getTowerScheduleManager() + .getCurrentTowerScheduleData().getEntranceFloorId()); + + unlockFloor(sender, sender.getServer().getTowerScheduleManager() + .getScheduleFloors()); + + CommandHandler.sendMessage(sender, translate("commands.tower.unlock_done")); + } + + public void unlockFloor(Player player, List floors){ + floors.stream() + .filter(id -> !player.getTowerManager().getRecordMap().containsKey(id)) + .forEach(id -> player.getTowerManager().getRecordMap().put(id, new TowerLevelRecord(id))); + } +} diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index f06d1bda6..ac2472192 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -73,6 +73,7 @@ public class GameData { private static final Int2ObjectMap rewardPreviewDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap towerFloorDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap towerLevelDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap towerScheduleDataMap = new Int2ObjectOpenHashMap<>(); // Cache private static Map> fetters = new HashMap<>(); @@ -327,4 +328,7 @@ public class GameData { public static Int2ObjectMap getTowerLevelDataMap(){ return towerLevelDataMap; } + public static Int2ObjectMap getTowerScheduleDataMap(){ + return towerScheduleDataMap; + } } diff --git a/src/main/java/emu/grasscutter/data/def/TowerScheduleData.java b/src/main/java/emu/grasscutter/data/def/TowerScheduleData.java new file mode 100644 index 000000000..017776c06 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/TowerScheduleData.java @@ -0,0 +1,70 @@ +package emu.grasscutter.data.def; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; + +import java.util.List; + +@ResourceType(name = "TowerScheduleExcelConfigData.json") +public class TowerScheduleData extends GameResource { + private int ScheduleId; + private List EntranceFloorId; + private List Schedules; + private int MonthlyLevelConfigId; + @Override + public int getId() { + return ScheduleId; + } + + @Override + public void onLoad() { + super.onLoad(); + this.Schedules = this.Schedules.stream() + .filter(item -> item.getFloorList().size() > 0) + .toList(); + } + + public int getScheduleId() { + return ScheduleId; + } + + public void setScheduleId(int scheduleId) { + ScheduleId = scheduleId; + } + + public List getEntranceFloorId() { + return EntranceFloorId; + } + + public void setEntranceFloorId(List entranceFloorId) { + EntranceFloorId = entranceFloorId; + } + + public List getSchedules() { + return Schedules; + } + + public void setSchedules(List schedules) { + Schedules = schedules; + } + + public int getMonthlyLevelConfigId() { + return MonthlyLevelConfigId; + } + + public void setMonthlyLevelConfigId(int monthlyLevelConfigId) { + MonthlyLevelConfigId = monthlyLevelConfigId; + } + + public static class ScheduleDetail{ + private List FloorList; + + public List getFloorList() { + return FloorList; + } + + public void setFloorList(List floorList) { + FloorList = floorList; + } + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java b/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java index 5b1ff7a30..c480d047f 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java +++ b/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java @@ -12,12 +12,18 @@ public class TowerDungeonSettleListener implements DungeonSettleListener { scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000); var towerManager = scene.getPlayers().get(0).getTowerManager(); - towerManager.notifyCurLevelRecordChangeWhenDone(); - scene.broadcastPacket(new PacketTowerFloorRecordChangeNotify(towerManager.getCurrentFloorId())); - scene.broadcastPacket(new PacketDungeonSettleNotify(scene.getChallenge(), - true, + towerManager.notifyCurLevelRecordChangeWhenDone(3); + scene.broadcastPacket(new PacketTowerFloorRecordChangeNotify( + towerManager.getCurrentFloorId(), + 3, + towerManager.canEnterScheduleFloor() + )); + + scene.broadcastPacket(new PacketDungeonSettleNotify( + scene.getChallenge(), + towerManager.hasNextFloor(), towerManager.hasNextLevel(), - towerManager.getNextFloorId() + towerManager.hasNextLevel() ? 0 : towerManager.getNextFloorId() )); } diff --git a/src/main/java/emu/grasscutter/game/tower/TowerLevelRecord.java b/src/main/java/emu/grasscutter/game/tower/TowerLevelRecord.java new file mode 100644 index 000000000..5a65f63ed --- /dev/null +++ b/src/main/java/emu/grasscutter/game/tower/TowerLevelRecord.java @@ -0,0 +1,64 @@ +package emu.grasscutter.game.tower; + +import dev.morphia.annotations.Entity; + +import java.util.HashMap; +import java.util.Map; + +@Entity +public class TowerLevelRecord { + /** + * floorId in config + */ + private int floorId; + /** + * LevelId - Stars + */ + private Map passedLevelMap; + + private int floorStarRewardProgress; + + public TowerLevelRecord setLevelStars(int levelId, int stars){ + passedLevelMap.put(levelId, stars); + return this; + } + + public int getStarCount() { + return passedLevelMap.values().stream().mapToInt(Integer::intValue).sum(); + } + + public TowerLevelRecord(){ + + } + + public TowerLevelRecord(int floorId){ + this.floorId = floorId; + this.passedLevelMap = new HashMap<>(); + this.floorStarRewardProgress = 0; + } + + public int getFloorId() { + return floorId; + } + + public void setFloorId(int floorId) { + this.floorId = floorId; + } + + public Map getPassedLevelMap() { + return passedLevelMap; + } + + public void setPassedLevelMap(Map passedLevelMap) { + this.passedLevelMap = passedLevelMap; + } + + public int getFloorStarRewardProgress() { + return floorStarRewardProgress; + } + + public void setFloorStarRewardProgress(int floorStarRewardProgress) { + this.floorStarRewardProgress = floorStarRewardProgress; + } + +} diff --git a/src/main/java/emu/grasscutter/game/tower/TowerManager.java b/src/main/java/emu/grasscutter/game/tower/TowerManager.java index 409549a1f..9346ffead 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerManager.java @@ -9,10 +9,12 @@ import emu.grasscutter.game.dungeons.TowerDungeonSettleListener; import emu.grasscutter.game.player.Player; import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; import emu.grasscutter.server.packet.send.PacketTowerCurLevelRecordChangeNotify; - import emu.grasscutter.server.packet.send.PacketTowerEnterLevelRsp; +import emu.grasscutter.server.packet.send.PacketTowerLevelStarCondNotify; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Entity public class TowerManager { @@ -26,11 +28,19 @@ public class TowerManager { this.player = player; } + /** + * the floor players chose + */ private int currentFloorId; private int currentLevel; @Transient private int currentLevelId; + /** + * floorId - Record + */ + private Map recordMap; + @Transient private int entryScene; @@ -38,7 +48,26 @@ public class TowerManager { return currentFloorId; } + public int getCurrentLevelId(){ + return this.currentLevelId + currentLevel; + } + + /** + * form 1-3 + */ + public int getCurrentLevel(){ + return currentLevel + 1; + } private static final List towerDungeonSettleListener = List.of(new TowerDungeonSettleListener()); + + public Map getRecordMap() { + if(recordMap == null){ + recordMap = new HashMap<>(); + recordMap.put(1001, new TowerLevelRecord(1001)); + } + return recordMap; + } + public void teamSelect(int floor, List> towerTeams) { var floorData = GameData.getTowerFloorDataMap().get(floor); @@ -54,51 +83,73 @@ public class TowerManager { entryScene = player.getSceneId(); } - player.getTeamManager().setupTemporaryTeam(towerTeams); } public void enterLevel(int enterPointId) { - var levelData = GameData.getTowerLevelDataMap().get(currentLevelId + currentLevel); + var levelData = GameData.getTowerLevelDataMap().get(getCurrentLevelId()); - this.currentLevel++; - var id = levelData.getDungeonId(); + var dungeonId = levelData.getDungeonId(); notifyCurLevelRecordChange(); // use team user choose player.getTeamManager().useTemporaryTeam(0); - player.getServer().getDungeonManager().handoffDungeon(player, id, + player.getServer().getDungeonManager().handoffDungeon(player, dungeonId, towerDungeonSettleListener); // make sure user can exit dungeon correctly player.getScene().setPrevScene(entryScene); player.getScene().setPrevScenePoint(enterPointId); - player.getSession().send(new PacketTowerEnterLevelRsp(currentFloorId, currentLevel)); + player.getSession().send(new PacketTowerEnterLevelRsp(currentFloorId, getCurrentLevel())); // stop using skill player.getSession().send(new PacketCanUseSkillNotify(false)); + // notify the cond of stars + player.getSession().send(new PacketTowerLevelStarCondNotify(currentFloorId, getCurrentLevel())); } public void notifyCurLevelRecordChange(){ - player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, currentLevel)); + player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, getCurrentLevel())); } - public void notifyCurLevelRecordChangeWhenDone(){ - player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, currentLevel + 1)); + public void notifyCurLevelRecordChangeWhenDone(int stars){ + if(!recordMap.containsKey(currentFloorId)){ + recordMap.put(currentFloorId, + new TowerLevelRecord(currentFloorId).setLevelStars(getCurrentLevelId(),stars)); + }else{ + recordMap.put(currentFloorId, + recordMap.get(currentFloorId).setLevelStars(getCurrentLevelId(),stars)); + } + + this.currentLevel++; + + if(!hasNextLevel()){ + // set up the next floor + recordMap.put(getNextFloorId(), new TowerLevelRecord(getNextFloorId())); + player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(getNextFloorId(), 1)); + }else{ + player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, getCurrentLevel())); + } } public boolean hasNextLevel(){ return this.currentLevel < 3; } - public int getNextFloorId() { - if(hasNextLevel()){ - return 0; - } - this.currentFloorId++; - return this.currentFloorId; + return player.getServer().getTowerScheduleManager().getNextFloorId(this.currentFloorId); + } + public boolean hasNextFloor(){ + return player.getServer().getTowerScheduleManager().getNextFloorId(this.currentFloorId) > 0; } public void clearEntry() { this.entryScene = 0; } + + public boolean canEnterScheduleFloor(){ + if(!recordMap.containsKey(player.getServer().getTowerScheduleManager().getLastEntranceFloor())){ + return false; + } + return recordMap.get(player.getServer().getTowerScheduleManager().getLastEntranceFloor()) + .getStarCount() >= 6; + } } diff --git a/src/main/java/emu/grasscutter/game/tower/TowerScheduleConfig.java b/src/main/java/emu/grasscutter/game/tower/TowerScheduleConfig.java new file mode 100644 index 000000000..35afbc7ba --- /dev/null +++ b/src/main/java/emu/grasscutter/game/tower/TowerScheduleConfig.java @@ -0,0 +1,35 @@ +package emu.grasscutter.game.tower; + +import java.util.Date; + +public class TowerScheduleConfig { + private int scheduleId; + + private Date scheduleStartTime; + private Date nextScheduleChangeTime; + + + public int getScheduleId() { + return scheduleId; + } + + public void setScheduleId(int scheduleId) { + this.scheduleId = scheduleId; + } + + public Date getScheduleStartTime() { + return scheduleStartTime; + } + + public void setScheduleStartTime(Date scheduleStartTime) { + this.scheduleStartTime = scheduleStartTime; + } + + public Date getNextScheduleChangeTime() { + return nextScheduleChangeTime; + } + + public void setNextScheduleChangeTime(Date nextScheduleChangeTime) { + this.nextScheduleChangeTime = nextScheduleChangeTime; + } +} diff --git a/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java b/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java new file mode 100644 index 000000000..33f5c158d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java @@ -0,0 +1,75 @@ +package emu.grasscutter.game.tower; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.TowerScheduleData; +import emu.grasscutter.server.game.GameServer; + +import java.io.FileReader; +import java.util.List; + +public class TowerScheduleManager { + private final GameServer gameServer; + + public GameServer getGameServer() { + return gameServer; + } + + public TowerScheduleManager(GameServer gameServer) { + this.gameServer = gameServer; + this.load(); + } + + private TowerScheduleConfig towerScheduleConfig; + + public synchronized void load(){ + try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "TowerSchedule.json")) { + towerScheduleConfig = Grasscutter.getGsonFactory().fromJson(fileReader, TowerScheduleConfig.class); + + } catch (Exception e) { + Grasscutter.getLogger().error("Unable to load tower schedule config.", e); + } + } + + public TowerScheduleConfig getTowerScheduleConfig() { + return towerScheduleConfig; + } + + public TowerScheduleData getCurrentTowerScheduleData(){ + var data = GameData.getTowerScheduleDataMap().get(towerScheduleConfig.getScheduleId()); + if(data == null){ + Grasscutter.getLogger().error("Could not get current tower schedule data by config:{}", towerScheduleConfig); + } + return data; + } + + public List getScheduleFloors() { + return getCurrentTowerScheduleData().getSchedules().get(0).getFloorList(); + } + + public int getNextFloorId(int floorId){ + var entranceFloors = getCurrentTowerScheduleData().getEntranceFloorId(); + var nextId = 0; + // find in entrance floors first + for(int i=0;i getBlocks() { return blocks; } @@ -237,16 +241,16 @@ public class SceneScriptManager { for (SceneSuite suite : group.suites) { suite.sceneMonsters = new ArrayList<>(suite.monsters.size()); - for (int id : suite.monsters) { - try { - SceneMonster monster = (SceneMonster) map.get(id); - if (monster != null) { - suite.sceneMonsters.add(monster); + suite.monsters.forEach(id -> { + Object objEntry = map.get(id.intValue()); + if (objEntry instanceof Map.Entry monsterEntry) { + Object monster = monsterEntry.getValue(); + if(monster instanceof SceneMonster sceneMonster){ + suite.sceneMonsters.add(sceneMonster); } - } catch (Exception e) { - continue; } - } + }); + suite.sceneGadgets = new ArrayList<>(suite.gadgets.size()); for (int id : suite.gadgets) { try { @@ -320,13 +324,15 @@ public class SceneScriptManager { } public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) { - this.currentGroup = group; - this.monsterSceneLimit = 0; var suite = group.getSuiteByIndex(suiteIndex); if(suite == null){ return; } - suite.sceneMonsters.forEach(mob -> spawnMonstersInGroup(group, mob)); + if(suite.sceneMonsters.size() > 0){ + this.currentGroup = group; + this.monsterSceneLimit = 0; + suite.sceneMonsters.forEach(mob -> spawnMonstersInGroup(group, mob)); + } } public void spawnMonstersInGroup(SceneGroup group) { @@ -401,6 +407,7 @@ public class SceneScriptManager { spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); }else if(this.monsterAlive.get() == 0){ // spawn the last turn of monsters + //callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs()); while(!this.monsterOrders.isEmpty()){ spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index 1b9badc11..1c4bbd0f2 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -118,7 +118,7 @@ public class ScriptLib { challengeIndex,groupId,ordersConfigId,tideCount,sceneLimit,param6); SceneGroup group = getSceneScriptManager().getGroupById(groupId); - + if (group == null || group.monsters == null) { return 1; } @@ -136,8 +136,7 @@ public class ScriptLib { if (group == null || group.monsters == null) { return 1; } - - // TODO just spawn all from group for now + this.getSceneScriptManager().spawnMonstersInGroup(group, suite); return 0; @@ -159,7 +158,13 @@ public class ScriptLib { if (group == null || group.monsters == null) { return 1; } - + + if(getSceneScriptManager().getScene().getChallenge() != null && + getSceneScriptManager().getScene().getChallenge().inProgress()) + { + return 0; + } + DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(), group); challenge.setChallengeId(challengeId); challenge.setChallengeIndex(challengeIndex); @@ -249,7 +254,7 @@ public class ScriptLib { var1); return (int) getSceneScriptManager().getScene().getEntities().values().stream() - .filter(e -> e instanceof EntityMonster) + .filter(e -> e instanceof EntityMonster && e.getGroupId() == getSceneScriptManager().getCurrentGroup().id) .count(); } public int SetMonsterBattleByGroup(int var1, int var2, int var3){ @@ -266,13 +271,11 @@ public class ScriptLib { return 0; } // 8-1 - public int GetGroupVariableValueByGroup(int var1, String var2, int var3){ - logger.debug("[LUA] Call GetGroupVariableValueByGroup with {},{},{}", - var1,var2,var3); + public int GetGroupVariableValueByGroup(String name, int groupId){ + logger.debug("[LUA] Call GetGroupVariableValueByGroup with {},{}", + name,groupId); - //TODO - - return getSceneScriptManager().getVariables().getOrDefault(var2, 0); + return getSceneScriptManager().getVariables().getOrDefault(name, 0); } public int SetIsAllowUseSkill(int canUse, int var2){ @@ -299,4 +302,11 @@ public class ScriptLib { return 0; } + public int SetGroupVariableValueByGroup(String key, int value, int groupId){ + logger.debug("[LUA] Call SetGroupVariableValueByGroup with {},{},{}", + key,value,groupId); + + return 0; + } + } diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index 7ce8488ef..cb0e4965d 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -15,6 +15,7 @@ import emu.grasscutter.game.managers.InventoryManager; import emu.grasscutter.game.managers.MultiplayerManager; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.shop.ShopManager; +import emu.grasscutter.game.tower.TowerScheduleManager; import emu.grasscutter.game.world.World; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; @@ -54,6 +55,7 @@ public final class GameServer extends KcpServer { private final DropManager dropManager; private final CombineManger combineManger; + private final TowerScheduleManager towerScheduleManager; public GameServer() { this(new InetSocketAddress( @@ -82,7 +84,7 @@ public final class GameServer extends KcpServer { this.dropManager = new DropManager(this); this.expeditionManager = new ExpeditionManager(this); this.combineManger = new CombineManger(this); - + this.towerScheduleManager = new TowerScheduleManager(this); // Hook into shutdown event. Runtime.getRuntime().addShutdownHook(new Thread(this::onServerShutdown)); } @@ -139,6 +141,10 @@ public final class GameServer extends KcpServer { return this.combineManger; } + public TowerScheduleManager getTowerScheduleManager() { + return towerScheduleManager; + } + public TaskMap getTaskMap() { return this.taskMap; } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerAllDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerAllDataReq.java index 2a9ef2004..38462882f 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerAllDataReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerAllDataReq.java @@ -11,7 +11,10 @@ public class HandlerTowerAllDataReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - session.send(new PacketTowerAllDataRsp()); + session.send(new PacketTowerAllDataRsp( + session.getServer().getTowerScheduleManager(), + session.getPlayer().getTowerManager() + )); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java index 479029243..56d844d8d 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDungeonSettleNotify.java @@ -46,7 +46,7 @@ public class PacketDungeonSettleNotify extends BasePacket { .setCount(1000) .build()) ; - if(nextFloorId > 0){ + if(nextFloorId > 0 && canJump){ towerLevelEndNotify.setNextFloorId(nextFloorId); } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java index d2d2376e6..654aa4a07 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java @@ -1,37 +1,64 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.def.TowerFloorData; +import emu.grasscutter.game.tower.TowerManager; +import emu.grasscutter.game.tower.TowerScheduleManager; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.TowerAllDataRspOuterClass.TowerAllDataRsp; import emu.grasscutter.net.proto.TowerCurLevelRecordOuterClass.TowerCurLevelRecord; import emu.grasscutter.net.proto.TowerFloorRecordOuterClass.TowerFloorRecord; +import emu.grasscutter.net.proto.TowerLevelRecordOuterClass; +import emu.grasscutter.utils.DateHelper; +import java.util.List; +import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.IntStream; public class PacketTowerAllDataRsp extends BasePacket { - public PacketTowerAllDataRsp() { + public PacketTowerAllDataRsp(TowerScheduleManager towerScheduleManager, TowerManager towerManager) { super(PacketOpcodes.TowerAllDataRsp); - var list = GameData.getTowerFloorDataMap().values().stream() - .map(TowerFloorData::getFloorId) - .map(id -> TowerFloorRecord.newBuilder().setFloorId(id).build()) - .collect(Collectors.toList()); + var recordList = towerManager.getRecordMap().values().stream() + .map(rec -> TowerFloorRecord.newBuilder() + .setFloorId(rec.getFloorId()) + .setFloorStarRewardProgress(rec.getFloorStarRewardProgress()) + .putAllPassedLevelMap(rec.getPassedLevelMap()) + .addAllPassedLevelRecordList(buildFromPassedLevelMap(rec.getPassedLevelMap())) + .build() + ) + .toList(); + + var openTimeMap = towerScheduleManager.getScheduleFloors().stream() + .collect(Collectors.toMap(x -> x, + y -> DateHelper.getUnixTime(towerScheduleManager.getTowerScheduleConfig() + .getScheduleStartTime())) + ); TowerAllDataRsp proto = TowerAllDataRsp.newBuilder() - .setTowerScheduleId(29) - .addAllTowerFloorRecordList(list) + .setTowerScheduleId(towerScheduleManager.getCurrentTowerScheduleData().getScheduleId()) + .addAllTowerFloorRecordList(recordList) .setCurLevelRecord(TowerCurLevelRecord.newBuilder().setIsEmpty(true)) - .setNextScheduleChangeTime(Integer.MAX_VALUE) - .putFloorOpenTimeMap(1024, 1630486800) - .putFloorOpenTimeMap(1025, 1630486800) - .putFloorOpenTimeMap(1026, 1630486800) - .putFloorOpenTimeMap(1027, 1630486800) - .setScheduleStartTime(1630486800) + .setScheduleStartTime(DateHelper.getUnixTime(towerScheduleManager.getTowerScheduleConfig() + .getScheduleStartTime())) + .setNextScheduleChangeTime(DateHelper.getUnixTime(towerScheduleManager.getTowerScheduleConfig() + .getNextScheduleChangeTime())) + .putAllFloorOpenTimeMap(openTimeMap) + .setIsFinishedEntranceFloor(towerManager.canEnterScheduleFloor()) .build(); this.setData(proto); } + + private List buildFromPassedLevelMap(Map map){ + return map.entrySet().stream() + .map(item -> TowerLevelRecordOuterClass.TowerLevelRecord.newBuilder() + .setLevelId(item.getKey()) + .addAllSatisfiedCondList(IntStream.range(1, item.getValue() + 1).boxed().toList()) + .build()) + .toList(); + + } + } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerFloorRecordChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerFloorRecordChangeNotify.java index c0ed414a8..5ab091901 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerFloorRecordChangeNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerFloorRecordChangeNotify.java @@ -8,13 +8,13 @@ import emu.grasscutter.net.proto.TowerLevelRecordOuterClass.TowerLevelRecord; public class PacketTowerFloorRecordChangeNotify extends BasePacket { - public PacketTowerFloorRecordChangeNotify(int floorId) { + public PacketTowerFloorRecordChangeNotify(int floorId, int stars, boolean canEnterScheduleFloor) { super(PacketOpcodes.TowerFloorRecordChangeNotify); TowerFloorRecordChangeNotify proto = TowerFloorRecordChangeNotify.newBuilder() .addTowerFloorRecordList(TowerFloorRecord.newBuilder() .setFloorId(floorId) - .setFloorStarRewardProgress(3) + .setFloorStarRewardProgress(stars) .addPassedLevelRecordList(TowerLevelRecord.newBuilder() .setLevelId(1) .addSatisfiedCondList(1) @@ -22,7 +22,7 @@ public class PacketTowerFloorRecordChangeNotify extends BasePacket { .addSatisfiedCondList(3) .build()) .build()) - .setIsFinishedEntranceFloor(true) + .setIsFinishedEntranceFloor(canEnterScheduleFloor) .build(); this.setData(proto); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerLevelStarCondNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerLevelStarCondNotify.java new file mode 100644 index 000000000..c2c301e4e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerLevelStarCondNotify.java @@ -0,0 +1,32 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TowerLevelStarCondDataOuterClass.TowerLevelStarCondData; +import emu.grasscutter.net.proto.TowerLevelStarCondNotifyOuterClass.TowerLevelStarCondNotify; + +public class PacketTowerLevelStarCondNotify extends BasePacket { + + public PacketTowerLevelStarCondNotify(int floorId, int levelIndex) { + super(PacketOpcodes.TowerLevelStarCondNotify); + + TowerLevelStarCondNotify proto = TowerLevelStarCondNotify.newBuilder() + .setFloorId(floorId) + .setLevelIndex(levelIndex) + .addCondDataList(TowerLevelStarCondData.newBuilder() + .setCondValue(1) + .build() + ) + .addCondDataList(TowerLevelStarCondData.newBuilder() + .setCondValue(2) + .build() + ) + .addCondDataList(TowerLevelStarCondData.newBuilder() + .setCondValue(3) + .build() + ) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/utils/DateHelper.java b/src/main/java/emu/grasscutter/utils/DateHelper.java index 7005d9457..1f1393760 100644 --- a/src/main/java/emu/grasscutter/utils/DateHelper.java +++ b/src/main/java/emu/grasscutter/utils/DateHelper.java @@ -1,7 +1,7 @@ package emu.grasscutter.utils; -import java.util.Date; import java.util.Calendar; +import java.util.Date; public final class DateHelper { public static Date onlyYearMonthDay(Date now) { @@ -13,4 +13,8 @@ public final class DateHelper { calendar.set(Calendar.MILLISECOND, 0); return calendar.getTime(); } + + public static int getUnixTime(Date localDateTime){ + return (int)(localDateTime.getTime() / 1000L); + } } diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 9a7485608..4ec17a214 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -312,6 +312,9 @@ "success": "Teleported %s to %s, %s, %s in scene %s", "description": "Change the player's position." }, + "tower": { + "unlock_done": "Abyss Corridor's Floors are all unlocked now." + }, "weather": { "usage": "Usage: weather [climateId]", "success": "Changed weather to %s with climate %s", From 5d1f49579bd6311165762bb4bd8c7e0769749ad1 Mon Sep 17 00:00:00 2001 From: Yazawazi <47273265+Yazawazi@users.noreply.github.com> Date: Sun, 8 May 2022 21:07:37 +0800 Subject: [PATCH 029/312] feature(task): Implement pause, resume and cancel Use as `pauseTask(taskName)`. They return boolean values to tell the developer if a timed task can be paused/resumed/cancelled properly. A little bit of testing shows that pausing and then resuming may execute the task multiple times. --- src/main/java/emu/grasscutter/task/Task.java | 2 -- .../java/emu/grasscutter/task/TaskMap.java | 36 ++++++++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/main/java/emu/grasscutter/task/Task.java b/src/main/java/emu/grasscutter/task/Task.java index 1f35d16ce..2c930c0e3 100644 --- a/src/main/java/emu/grasscutter/task/Task.java +++ b/src/main/java/emu/grasscutter/task/Task.java @@ -1,7 +1,5 @@ package emu.grasscutter.task; -import org.quartz.JobDataMap; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/src/main/java/emu/grasscutter/task/TaskMap.java b/src/main/java/emu/grasscutter/task/TaskMap.java index fe067e795..a4b2ff02f 100644 --- a/src/main/java/emu/grasscutter/task/TaskMap.java +++ b/src/main/java/emu/grasscutter/task/TaskMap.java @@ -67,6 +67,40 @@ public final class TaskMap { return this; } + public boolean pauseTask(String taskName) { + try { + Scheduler scheduler = schedulerFactory.getScheduler(); + scheduler.pauseJob(new JobKey(taskName)); + } catch (SchedulerException e) { + e.printStackTrace(); + return false; + } + return true; + } + + public boolean resumeTask(String taskName) { + try { + Scheduler scheduler = schedulerFactory.getScheduler(); + scheduler.resumeJob(new JobKey(taskName)); + } catch (SchedulerException e) { + e.printStackTrace(); + return false; + } + return true; + } + + public boolean cancelTask(String taskName) { + Task task = this.annotations.get(taskName); + if (task == null) return false; + try { + this.unregisterTask(this.tasks.get(taskName)); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + return true; + } + public TaskMap registerTask(String taskName, TaskHandler task) { Task annotation = task.getClass().getAnnotation(Task.class); this.annotations.put(taskName, annotation); @@ -116,7 +150,7 @@ public final class TaskMap { classes.forEach(annotated -> { try { Task taskData = annotated.getAnnotation(Task.class); - Object object = annotated.newInstance(); + Object object = annotated.getDeclaredConstructor().newInstance(); if (object instanceof TaskHandler) { this.registerTask(taskData.taskName(), (TaskHandler) object); if (taskData.executeImmediatelyAfterReset()) { From 5f8011f0ecbbac0587e409df9478a511e8fc2f01 Mon Sep 17 00:00:00 2001 From: ImmuState Date: Sun, 8 May 2022 08:44:05 -0700 Subject: [PATCH 030/312] Added to ability to specify main and substats for /giveart via names instead of IDs. --- .../command/commands/GiveArtifactCommand.java | 116 +++++++++++++++++- 1 file changed, 111 insertions(+), 5 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java index b87642bb2..3b8279d98 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java @@ -9,28 +9,109 @@ import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.ItemType; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.game.inventory.EquipType; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; +import static java.util.Map.entry; import static emu.grasscutter.utils.Language.translate; -@Command(label = "giveart", usage = "giveart [[,]]... [level]", aliases = {"gart"}, permission = "player.giveart", description = "commands.giveArtifact.description") +@Command(label = "giveart", usage = "giveart [[,]]... [level]", description = "Gives the player a specified artifact", aliases = {"gart"}, permission = "player.giveart") public final class GiveArtifactCommand implements CommandHandler { + private static final Map> mainPropMap = Map.ofEntries( + entry("hp", Map.ofEntries(entry(EquipType.EQUIP_BRACER, 14001))), + entry("hp%", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10980), entry(EquipType.EQUIP_RING, 50980), entry(EquipType.EQUIP_DRESS, 30980))), + entry("atk", Map.ofEntries(entry(EquipType.EQUIP_NECKLACE, 12001))), + entry("atk%", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10990), entry(EquipType.EQUIP_RING, 50990), entry(EquipType.EQUIP_DRESS, 30990))), + entry("def%", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10970), entry(EquipType.EQUIP_RING, 50970), entry(EquipType.EQUIP_DRESS, 30970))), + entry("er", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10960))), + entry("em", Map.ofEntries(entry(EquipType.EQUIP_SHOES, 10950), entry(EquipType.EQUIP_RING, 50880), entry(EquipType.EQUIP_DRESS, 30930))), + entry("hb", Map.ofEntries(entry(EquipType.EQUIP_DRESS, 30940))), + entry("cdmg", Map.ofEntries(entry(EquipType.EQUIP_DRESS, 30950))), + entry("cr", Map.ofEntries(entry(EquipType.EQUIP_DRESS, 30960))), + entry("phys%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50890))), + entry("dendro%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50900))), + entry("geo%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50910))), + entry("anemo%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50920))), + entry("hydro%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50930))), + entry("cryo%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50940))), + entry("electro%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50950))), + entry("pyro%", Map.ofEntries(entry(EquipType.EQUIP_RING, 50960))) + ); + private static final Map appendPropMap = Map.ofEntries( + entry("hp", "0102"), + entry("hp%", "0103"), + entry("atk", "0105"), + entry("atk%", "0106"), + entry("def", "0108"), + entry("def%", "0109"), + entry("er", "0123"), + entry("em", "0124"), + entry("cr", "0120"), + entry("cdmg", "0122") + ); + + private int getAppendPropId(String substatText, ItemData itemData) { + int res; + + // If the given substat text is an integer, we just use that + // as the append prop ID. + try { + res = Integer.parseInt(substatText); + return res; + } + catch (NumberFormatException ignores) { + // No need to handle this here. We just continue with the + // possibility of the argument being a substat string. + } + + // If the argument was not an integer, we try to determine + // the append prop ID from the given text + artifact information. + // A substat string has the format `substat_tier`. + String[] substatArgs = substatText.split("_"); + if (substatArgs.length != 2) { + throw new IllegalArgumentException(); + } + + String substatType = substatArgs[0]; + int substatTier = Integer.parseInt(substatArgs[1]); + + // Check if the specified tier is legal for the artifact rarity. + if (substatTier < 1 || substatTier > 4) { + throw new IllegalArgumentException(); + } + if (itemData.getRankLevel() == 1 && substatTier > 2) { + throw new IllegalArgumentException(); + } + if (itemData.getRankLevel() == 2 && substatTier > 3) { + throw new IllegalArgumentException(); + } + + // Check if the given substat type string is a legal stat. + if (!appendPropMap.containsKey(substatType)) { + throw new IllegalArgumentException(); + } + + // Build the append prop ID. + return Integer.parseInt(Integer.toString(itemData.getRankLevel()) + appendPropMap.get(substatType) + Integer.toString(substatTier)); + } @Override public void execute(Player sender, Player targetPlayer, List args) { + // Sanity checks if (targetPlayer == null) { CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); return; } - if (args.size() < 2) { CommandHandler.sendMessage(sender, translate("commands.giveArtifact.usage")); return; } + // Get the artifact piece ID from the arguments. int itemId; try { itemId = Integer.parseInt(args.remove(0)); @@ -38,20 +119,35 @@ public final class GiveArtifactCommand implements CommandHandler { CommandHandler.sendMessage(sender, translate("commands.giveArtifact.id_error")); return; } + ItemData itemData = GameData.getItemDataMap().get(itemId); if (itemData.getItemType() != ItemType.ITEM_RELIQUARY) { CommandHandler.sendMessage(sender, translate("commands.giveArtifact.id_error")); return; } + // Get the main stat from the arguments. + // If the given argument is an integer, we use that. + // If not, we check if the argument string is in the main prop map. + String mainPropIdString = args.remove(0); int mainPropId; + try { - mainPropId = Integer.parseInt(args.remove(0)); + mainPropId = Integer.parseInt(mainPropIdString); } catch (NumberFormatException ignored) { + mainPropId = -1; + } + + if (mainPropMap.containsKey(mainPropIdString) && mainPropMap.get(mainPropIdString).containsKey(itemData.getEquipType())) { + mainPropId = mainPropMap.get(mainPropIdString).get(itemData.getEquipType()); + } + + if (mainPropId == -1) { CommandHandler.sendMessage(sender, translate("commands.generic.execution.argument_error")); return; } + // Get the level from the arguments. int level = 1; try { int last = Integer.parseInt(args.get(args.size()-1)); @@ -62,9 +158,13 @@ public final class GiveArtifactCommand implements CommandHandler { } catch (NumberFormatException ignored) { // Could be a stat,times string so no need to panic } - List appendPropIdList = new ArrayList<>(); + // Get substats. + ArrayList appendPropIdList = new ArrayList<>(); try { + // Every remaining argument is a substat. args.forEach(it -> { + // The substat syntax permits specifying a number of rolls for the given + // substat. Split the string into stat and number if that is the case here. String[] arr; int n = 1; if ((arr = it.split(",")).length == 2) { @@ -74,13 +174,19 @@ public final class GiveArtifactCommand implements CommandHandler { n = 200; } } - appendPropIdList.addAll(Collections.nCopies(n, Integer.parseInt(it))); + + // Determine the substat ID. + int appendPropId = getAppendPropId(it, itemData); + + // Add the current substat. + appendPropIdList.addAll(Collections.nCopies(n, appendPropId)); }); } catch (Exception ignored) { CommandHandler.sendMessage(sender, translate("commands.execution.argument_error")); return; } + // Create item for the artifact. GameItem item = new GameItem(itemData); item.setLevel(level); item.setMainPropId(mainPropId); From 72e8a976eaa01ae046a6321a096988cbe3a68156 Mon Sep 17 00:00:00 2001 From: ImmuState Date: Sun, 8 May 2022 09:09:09 -0700 Subject: [PATCH 031/312] Fix invalid translation key. --- .../emu/grasscutter/command/commands/GiveArtifactCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java index 3b8279d98..23efd0d79 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java @@ -143,7 +143,7 @@ public final class GiveArtifactCommand implements CommandHandler { } if (mainPropId == -1) { - CommandHandler.sendMessage(sender, translate("commands.generic.execution.argument_error")); + CommandHandler.sendMessage(sender, translate("commands.execution.argument_error")); return; } From 935bb4b523ae59b6bc85889c343444bc7f0cf5b5 Mon Sep 17 00:00:00 2001 From: ImmuState Date: Sun, 8 May 2022 09:18:53 -0700 Subject: [PATCH 032/312] Fix incorrect @Command annotation. --- .../emu/grasscutter/command/commands/GiveArtifactCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java index 23efd0d79..6a0e3ce24 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java @@ -19,7 +19,7 @@ import static java.util.Map.entry; import static emu.grasscutter.utils.Language.translate; -@Command(label = "giveart", usage = "giveart [[,]]... [level]", description = "Gives the player a specified artifact", aliases = {"gart"}, permission = "player.giveart") +@Command(label = "giveart", usage = "giveart [[,]]... [level]", aliases = {"gart"}, permission = "player.giveart", description = "commands.giveArtifact.description") public final class GiveArtifactCommand implements CommandHandler { private static final Map> mainPropMap = Map.ofEntries( entry("hp", Map.ofEntries(entry(EquipType.EQUIP_BRACER, 14001))), From 9869e4368dc90a34a0b2de7795ba155d25d48530 Mon Sep 17 00:00:00 2001 From: ImmuState Date: Sun, 8 May 2022 13:25:47 -0700 Subject: [PATCH 033/312] -Make the _tier suffix optional. --- .../command/commands/GiveArtifactCommand.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java index 6a0e3ce24..cf479ed57 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java @@ -70,15 +70,27 @@ public final class GiveArtifactCommand implements CommandHandler { // If the argument was not an integer, we try to determine // the append prop ID from the given text + artifact information. - // A substat string has the format `substat_tier`. + // A substat string has the format `substat_tier`, with the + // `_tier` part being optional. String[] substatArgs = substatText.split("_"); - if (substatArgs.length != 2) { + String substatType; + int substatTier; + + if (substatArgs.length == 1) { + substatType = substatArgs[0]; + substatTier = + itemData.getRankLevel() == 1 ? 2 + : itemData.getRankLevel() == 2 ? 3 + : 4; + } + else if (substatArgs.length == 2) { + substatType = substatArgs[0]; + substatTier = Integer.parseInt(substatArgs[1]); + } + else { throw new IllegalArgumentException(); } - String substatType = substatArgs[0]; - int substatTier = Integer.parseInt(substatArgs[1]); - // Check if the specified tier is legal for the artifact rarity. if (substatTier < 1 || substatTier > 4) { throw new IllegalArgumentException(); From 056b5b80c88188a9d885e20ae31c3cf5177bc794 Mon Sep 17 00:00:00 2001 From: Shirakami Ling Date: Mon, 9 May 2022 06:57:39 +0800 Subject: [PATCH 034/312] Change the license type in zh-CN.json. --- src/main/resources/languages/zh-CN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 4e4929aee..89d1d6a90 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -36,7 +36,7 @@ } }, "status": { - "free_software": "Grasscutter 是免费开源软件,遵循Apache-2.0 license。如果您是付费购买的,那您已经被骗了。项目地址:Github:https://github.com/Grasscutters/Grasscutter", + "free_software": "Grasscutter 是免费开源软件,遵循AGPL-3.0 license。如果您是付费购买的,那您已经被骗了。项目地址:Github:https://github.com/Grasscutters/Grasscutter", "starting": "正在启动 Grasscutter...", "shutdown": "正在关闭...", "done": "加载完成!输入 \"help\" 查看命令列表", From 65c93a747c33851fb09f8d35af2446d1753920a1 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Mon, 9 May 2022 15:39:49 +0800 Subject: [PATCH 035/312] Support Team Toggle in Tower & Refactor MonsterTide --- .../dungeons/TowerDungeonSettleListener.java | 4 + .../game/entity/EntityMonster.java | 4 +- .../grasscutter/game/tower/TowerManager.java | 11 +- .../game/tower/TowerScheduleManager.java | 5 +- .../scripts/SceneScriptManager.java | 182 ++++++++---------- .../emu/grasscutter/scripts/ScriptLib.java | 50 ++++- .../scripts/data/SceneTrigger.java | 14 ++ .../service/ScriptMonsterSpawnService.java | 73 +++++++ .../service/ScriptMonsterTideService.java | 74 +++++++ ...acketTowerMiddleLevelChangeTeamNotify.java | 18 ++ 10 files changed, 320 insertions(+), 115 deletions(-) create mode 100644 src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java create mode 100644 src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketTowerMiddleLevelChangeTeamNotify.java diff --git a/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java b/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java index c480d047f..3f212ce4a 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java +++ b/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java @@ -9,6 +9,10 @@ public class TowerDungeonSettleListener implements DungeonSettleListener { @Override public void onDungeonSettle(Scene scene) { + if(scene.getScriptManager().getVariables().containsKey("stage") + && scene.getScriptManager().getVariables().get("stage") == 1){ + return; + } scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000); var towerManager = scene.getPlayers().get(0).getTowerManager(); diff --git a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java index 0ae6f356b..f8a96a808 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java @@ -117,7 +117,9 @@ public class EntityMonster extends GameEntity { this.getScene().getDeadSpawnedEntities().add(getSpawnEntry()); } if (getScene().getScriptManager().isInit() && this.getGroupId() > 0) { - getScene().getScriptManager().onMonsterDie(); + if(getScene().getScriptManager().getScriptMonsterSpawnService() != null){ + getScene().getScriptManager().getScriptMonsterSpawnService().onMonsterDead(this); + } getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, null); } if (getScene().getChallenge() != null && getScene().getChallenge().getGroup().id == this.getGroupId()) { diff --git a/src/main/java/emu/grasscutter/game/tower/TowerManager.java b/src/main/java/emu/grasscutter/game/tower/TowerManager.java index 9346ffead..cac848ea6 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerManager.java @@ -7,10 +7,7 @@ import emu.grasscutter.data.def.TowerLevelData; import emu.grasscutter.game.dungeons.DungeonSettleListener; import emu.grasscutter.game.dungeons.TowerDungeonSettleListener; import emu.grasscutter.game.player.Player; -import emu.grasscutter.server.packet.send.PacketCanUseSkillNotify; -import emu.grasscutter.server.packet.send.PacketTowerCurLevelRecordChangeNotify; -import emu.grasscutter.server.packet.send.PacketTowerEnterLevelRsp; -import emu.grasscutter.server.packet.send.PacketTowerLevelStarCondNotify; +import emu.grasscutter.server.packet.send.*; import java.util.HashMap; import java.util.List; @@ -152,4 +149,10 @@ public class TowerManager { return recordMap.get(player.getServer().getTowerScheduleManager().getLastEntranceFloor()) .getStarCount() >= 6; } + + public void mirrorTeamSetUp(int teamId) { + // use team user choose + player.getTeamManager().useTemporaryTeam(teamId); + player.sendPacket(new PacketTowerMiddleLevelChangeTeamNotify()); + } } diff --git a/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java b/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java index 33f5c158d..952acd806 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java @@ -49,6 +49,7 @@ public class TowerScheduleManager { public int getNextFloorId(int floorId){ var entranceFloors = getCurrentTowerScheduleData().getEntranceFloorId(); + var scheduleFloors = getScheduleFloors(); var nextId = 0; // find in entrance floors first for(int i=0;i variables; - private Bindings bindings; private SceneConfig config; private List blocks; private boolean isInit; - - private final Int2ObjectOpenHashMap> triggers; + /** + * SceneTrigger Set + */ + private final Map triggers; + /** + * current triggers controlled by RefreshGroup + */ + private final Int2ObjectOpenHashMap> currentTriggers; private final Int2ObjectOpenHashMap regions; + private Map sceneGroups; private SceneGroup currentGroup; - private AtomicInteger monsterAlive; - private AtomicInteger monsterTideCount; - private int monsterSceneLimit; - private ConcurrentLinkedQueue monsterOrders; + private ScriptMonsterTideService scriptMonsterTideService; + private ScriptMonsterSpawnService scriptMonsterSpawnService; public SceneScriptManager(Scene scene) { this.scene = scene; this.scriptLib = new ScriptLib(this); this.scriptLibLua = CoerceJavaToLua.coerce(this.scriptLib); - this.triggers = new Int2ObjectOpenHashMap<>(); + this.triggers = new HashMap<>(); + this.currentTriggers = new Int2ObjectOpenHashMap<>(); + this.regions = new Int2ObjectOpenHashMap<>(); this.variables = new HashMap<>(); - + this.sceneGroups = new HashMap<>(); + this.scriptMonsterSpawnService = new ScriptMonsterSpawnService(this); + // TEMPORARY if (this.getScene().getId() < 10) { return; @@ -103,17 +108,35 @@ public class SceneScriptManager { } public Set getTriggersByEvent(int eventId) { - return triggers.computeIfAbsent(eventId, e -> new HashSet<>()); + return currentTriggers.computeIfAbsent(eventId, e -> new HashSet<>()); } - public void registerTrigger(SceneTrigger trigger) { + this.triggers.put(trigger.name, trigger); getTriggersByEvent(trigger.event).add(trigger); } public void deregisterTrigger(SceneTrigger trigger) { + this.triggers.remove(trigger.name); getTriggersByEvent(trigger.event).remove(trigger); } - + public void resetTriggers(List triggerNames) { + for(var name : triggerNames){ + var instance = triggers.get(name); + this.currentTriggers.get(instance.event).clear(); + this.currentTriggers.get(instance.event).add(instance); + } + } + public void refreshGroup(SceneGroup group, int suiteIndex){ + var suite = group.getSuiteByIndex(suiteIndex); + if(suite == null){ + return; + } + if(suite.triggers.size() > 0){ + resetTriggers(suite.triggers); + } + spawnMonstersInGroup(group, suite); + spawnGadgetsInGroup(group, suite); + } public SceneRegion getRegionById(int id) { return regions.get(id); } @@ -263,6 +286,7 @@ public class SceneScriptManager { } } } + this.sceneGroups.put(group.id, group); } catch (ScriptException e) { Grasscutter.getLogger().error("Error loading group " + group.id + " in scene " + getScene().getId(), e); } @@ -322,96 +346,36 @@ public class SceneScriptManager { this.callEvent(EventType.EVENT_GADGET_CREATE, new ScriptArgs(entity.getConfigId())); } } - + public void spawnMonstersInGroup(SceneGroup group, int suiteIndex) { var suite = group.getSuiteByIndex(suiteIndex); if(suite == null){ return; } - if(suite.sceneMonsters.size() > 0){ - this.currentGroup = group; - this.monsterSceneLimit = 0; - suite.sceneMonsters.forEach(mob -> spawnMonstersInGroup(group, mob)); + spawnMonstersInGroup(group, suite); + } + public void spawnMonstersInGroup(SceneGroup group, SceneSuite suite) { + if(suite == null || suite.sceneMonsters.size() <= 0){ + return; } + this.currentGroup = group; + suite.sceneMonsters.forEach(mob -> this.scriptMonsterSpawnService.spawnMonster(group.id, mob)); } public void spawnMonstersInGroup(SceneGroup group) { this.currentGroup = group; - this.monsterSceneLimit = 0; - group.monsters.values().forEach(mob -> spawnMonstersInGroup(group, mob)); + group.monsters.values().forEach(mob -> this.scriptMonsterSpawnService.spawnMonster(group.id, mob)); } - public void spawnMonstersInGroup(SceneGroup group,Integer[] ordersConfigId, int tideCount, int sceneLimit) { + + public void startMonsterTideInGroup(SceneGroup group, Integer[] ordersConfigId, int tideCount, int sceneLimit) { this.currentGroup = group; - this.monsterSceneLimit = sceneLimit; - this.monsterTideCount = new AtomicInteger(tideCount); - this.monsterAlive = new AtomicInteger(0); - this.monsterOrders = new ConcurrentLinkedQueue<>(List.of(ordersConfigId)); + this.scriptMonsterTideService = + new ScriptMonsterTideService(this, group, tideCount, sceneLimit, ordersConfigId); - // add the last turn - group.monsters.keySet().stream() - .filter(i -> !this.monsterOrders.contains(i)) - .forEach(this.monsterOrders::add); - for (int i = 0; i < sceneLimit; i++) { - spawnMonstersInGroup(group, group.monsters.get(this.monsterOrders.poll())); - } } - public void spawnMonstersInGroup(SceneGroup group, SceneMonster monster) { - if(monster == null){ - return; - } - if(this.monsterSceneLimit > 0){ - this.monsterTideCount.decrementAndGet(); - this.monsterAlive.incrementAndGet(); - } - - MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); - - if (data == null) { - return; - } - - // Calculate level - int level = monster.level; - - if (getScene().getDungeonData() != null) { - level = getScene().getDungeonData().getShowLevel(); - } else if (getScene().getWorld().getWorldLevel() > 0) { - WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(getScene().getWorld().getWorldLevel()); - - if (worldLevelData != null) { - level = worldLevelData.getMonsterLevel(); - } - } - - // Spawn mob - EntityMonster entity = new EntityMonster(getScene(), data, monster.pos, level); - entity.getRotation().set(monster.rot); - entity.setGroupId(group.id); - entity.setConfigId(monster.config_id); - - getScene().addEntity(entity); - - callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId())); - } - - public void onMonsterDie(){ - if(this.monsterSceneLimit <= 0){ - return; - } - if(this.monsterAlive.decrementAndGet() >= this.monsterSceneLimit) { - // maybe not happen - return; - } - if(this.monsterTideCount.get() > 0){ - // add more - spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); - }else if(this.monsterAlive.get() == 0){ - // spawn the last turn of monsters - //callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs()); - while(!this.monsterOrders.isEmpty()){ - spawnMonstersInGroup(this.currentGroup, this.currentGroup.monsters.get(this.monsterOrders.poll())); - } - } + public void spawnMonstersByConfigId(int configId, int delayTime) { + // TODO delay + this.scriptMonsterSpawnService.spawnMonster(this.currentGroup.id, this.currentGroup.monsters.get(configId)); } // Events @@ -432,17 +396,35 @@ public class SceneScriptManager { args = CoerceJavaToLua.coerce(params); } - ret = condition.call(this.getScriptLibLua(), args); + ret = safetyCall(trigger.condition, condition, args); } - if (ret.checkboolean() == true) { + if (ret.isboolean() && ret.checkboolean()) { LuaValue action = (LuaValue) this.getBindings().get(trigger.action); - action.call(this.getScriptLibLua(), LuaValue.NIL); + var arg = new ScriptArgs(); + arg.param2 = 100; + var args = CoerceJavaToLua.coerce(arg); + safetyCall(trigger.action, action, args); } + //TODO some ret may not bool } } -// public LuaValue safetyCall(){ -// -// } + public LuaValue safetyCall(String name, LuaValue func, LuaValue args){ + try{ + return func.call(this.getScriptLibLua(), args); + }catch (LuaError error){ + ScriptLib.logger.error("[LUA] call trigger failed {},{}",name,args,error); + return LuaValue.valueOf(-1); + } + } + + public ScriptMonsterTideService getScriptMonsterTideService() { + return scriptMonsterTideService; + } + + public ScriptMonsterSpawnService getScriptMonsterSpawnService() { + return scriptMonsterSpawnService; + } + } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index 1c4bbd0f2..0d686dd5a 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -28,7 +28,17 @@ public class ScriptLib { public SceneScriptManager getSceneScriptManager() { return sceneScriptManager; } - + + private String printTable(LuaTable table){ + StringBuilder sb = new StringBuilder(); + sb.append("{"); + for(var meta : table.keys()){ + sb.append(meta).append(":").append(table.get(meta)).append(","); + } + sb.append("}"); + return sb.toString(); + } + public int SetGadgetStateByConfigId(int configId, int gadgetState) { logger.debug("[LUA] Call SetGadgetStateByConfigId with {},{}", configId,gadgetState); @@ -123,7 +133,7 @@ public class ScriptLib { return 1; } - this.getSceneScriptManager().spawnMonstersInGroup(group, ordersConfigId, tideCount, sceneLimit); + this.getSceneScriptManager().startMonsterTideInGroup(group, ordersConfigId, tideCount, sceneLimit); return 0; } @@ -204,10 +214,13 @@ public class ScriptLib { getSceneScriptManager().getVariables().put(var, getSceneScriptManager().getVariables().get(var) + value); return LuaValue.ZERO; } - + + /** + * Set the actions and triggers to designated group + */ public int RefreshGroup(LuaTable table) { logger.debug("[LUA] Call RefreshGroup with {}", - table); + printTable(table)); // Kill and Respawn? int groupId = table.get("group_id").toint(); int suite = table.get("suite").toint(); @@ -218,8 +231,7 @@ public class ScriptLib { return 1; } - this.getSceneScriptManager().spawnMonstersInGroup(group, suite); - this.getSceneScriptManager().spawnGadgetsInGroup(group, suite); + getSceneScriptManager().refreshGroup(group, suite); return 0; } @@ -260,7 +272,7 @@ public class ScriptLib { public int SetMonsterBattleByGroup(int var1, int var2, int var3){ logger.debug("[LUA] Call SetMonsterBattleByGroup with {},{},{}", var1,var2,var3); - + // TODO return 0; } @@ -270,7 +282,7 @@ public class ScriptLib { return 0; } - // 8-1 + public int GetGroupVariableValueByGroup(String name, int groupId){ logger.debug("[LUA] Call GetGroupVariableValueByGroup with {},{}", name,groupId); @@ -288,7 +300,7 @@ public class ScriptLib { public int KillEntityByConfigId(LuaTable table){ logger.debug("[LUA] Call KillEntityByConfigId with {}", - table); + printTable(table)); var configId = table.get("config_id"); if(configId == LuaValue.NIL){ return 1; @@ -306,6 +318,26 @@ public class ScriptLib { logger.debug("[LUA] Call SetGroupVariableValueByGroup with {},{},{}", key,value,groupId); + getSceneScriptManager().getVariables().put(key, value); + return 0; + } + + public int CreateMonster(LuaTable table){ + logger.debug("[LUA] Call CreateMonster with {}", + printTable(table)); + var configId = table.get("config_id").toint(); + var delayTime = table.get("delay_time").toint(); + + getSceneScriptManager().spawnMonstersByConfigId(configId, delayTime); + return 0; + } + + public int TowerMirrorTeamSetUp(int team, int var1) { + logger.debug("[LUA] Call TowerMirrorTeamSetUp with {},{}", + team,var1); + + getSceneScriptManager().getScene().getPlayers().get(0).getTowerManager().mirrorTeamSetUp(team-1); + return 0; } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java index a1603b1e6..a627f67c4 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java @@ -7,4 +7,18 @@ public class SceneTrigger { public String source; public String condition; public String action; + + @Override + public boolean equals(Object obj) { + if(obj instanceof SceneTrigger sceneTrigger){ + return this.name.equals(sceneTrigger.name); + } + return super.equals(obj); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + } diff --git a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java new file mode 100644 index 000000000..dda0d4732 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java @@ -0,0 +1,73 @@ +package emu.grasscutter.scripts.service; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.MonsterData; +import emu.grasscutter.data.def.WorldLevelData; +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.scripts.SceneScriptManager; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.SceneMonster; +import emu.grasscutter.scripts.data.ScriptArgs; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class ScriptMonsterSpawnService { + + private final SceneScriptManager sceneScriptManager; + private final List> onMonsterCreatedListener = new ArrayList<>(); + + private final List> onMonsterDeadListener = new ArrayList<>(); + + public ScriptMonsterSpawnService(SceneScriptManager sceneScriptManager){ + this.sceneScriptManager = sceneScriptManager; + } + + public void addMonsterCreatedListener(Consumer consumer){ + onMonsterCreatedListener.add(consumer); + } + public void addMonsterDeadListener(Consumer consumer){ + onMonsterCreatedListener.add(consumer); + } + + public void onMonsterDead(EntityMonster entityMonster){ + onMonsterCreatedListener.stream().forEach(l -> l.accept(entityMonster)); + } + public void spawnMonster(int groupId, SceneMonster monster) { + if(monster == null){ + return; + } + + MonsterData data = GameData.getMonsterDataMap().get(monster.monster_id); + + if (data == null) { + return; + } + + // Calculate level + int level = monster.level; + + if (sceneScriptManager.getScene().getDungeonData() != null) { + level = sceneScriptManager.getScene().getDungeonData().getShowLevel(); + } else if (sceneScriptManager.getScene().getWorld().getWorldLevel() > 0) { + WorldLevelData worldLevelData = GameData.getWorldLevelDataMap().get(sceneScriptManager.getScene().getWorld().getWorldLevel()); + + if (worldLevelData != null) { + level = worldLevelData.getMonsterLevel(); + } + } + + // Spawn mob + EntityMonster entity = new EntityMonster(sceneScriptManager.getScene(), data, monster.pos, level); + entity.getRotation().set(monster.rot); + entity.setGroupId(groupId); + entity.setConfigId(monster.config_id); + + onMonsterCreatedListener.forEach(action -> action.accept(entity)); + + sceneScriptManager.getScene().addEntity(entity); + + sceneScriptManager.callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(entity.getConfigId())); + } +} diff --git a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java new file mode 100644 index 000000000..117297f15 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java @@ -0,0 +1,74 @@ +package emu.grasscutter.scripts.service; + +import emu.grasscutter.game.entity.EntityMonster; +import emu.grasscutter.scripts.SceneScriptManager; +import emu.grasscutter.scripts.constants.EventType; +import emu.grasscutter.scripts.data.SceneGroup; +import emu.grasscutter.scripts.data.SceneMonster; +import emu.grasscutter.scripts.data.ScriptArgs; + +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; + +public class ScriptMonsterTideService { + private final SceneScriptManager sceneScriptManager; + private final SceneGroup currentGroup; + private final AtomicInteger monsterAlive; + private final AtomicInteger monsterTideCount; + private final AtomicInteger monsterKillCount; + private final int monsterSceneLimit; + private final ConcurrentLinkedQueue monsterConfigOrders; + + public ScriptMonsterTideService(SceneScriptManager sceneScriptManager, + SceneGroup group, int tideCount, int monsterSceneLimit, Integer[] ordersConfigId){ + this.sceneScriptManager = sceneScriptManager; + this.currentGroup = group; + this.monsterSceneLimit = monsterSceneLimit; + this.monsterTideCount = new AtomicInteger(tideCount); + this.monsterKillCount = new AtomicInteger(0); + this.monsterAlive = new AtomicInteger(0); + this.monsterConfigOrders = new ConcurrentLinkedQueue<>(List.of(ordersConfigId)); + + this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterCreatedListener(this::onMonsterCreated); + this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterDeadListener(this::onMonsterDead); + // spawn the first turn + for (int i = 0; i < this.monsterSceneLimit; i++) { + this.sceneScriptManager.getScriptMonsterSpawnService().spawnMonster(group.id, getNextMonster()); + } + } + + public void onMonsterCreated(EntityMonster entityMonster){ + if(this.monsterSceneLimit > 0){ + this.monsterTideCount.decrementAndGet(); + this.monsterAlive.incrementAndGet(); + } + } + + public SceneMonster getNextMonster(){ + var nextId = this.monsterConfigOrders.poll(); + if(currentGroup.monsters.containsKey(nextId)){ + return currentGroup.monsters.get(nextId); + } + // TODO some monster config_id do not exist in groups, so temporarily set it to the first + return currentGroup.monsters.values().stream().findFirst().orElse(null); + } + + public void onMonsterDead(EntityMonster entityMonster){ + if(this.monsterSceneLimit <= 0){ + return; + } + if(this.monsterAlive.decrementAndGet() >= this.monsterSceneLimit) { + // maybe not happen + return; + } + this.monsterKillCount.incrementAndGet(); + if(this.monsterTideCount.get() > 0){ + // add more + this.sceneScriptManager.getScriptMonsterSpawnService().spawnMonster(this.currentGroup.id, getNextMonster()); + }else if(this.monsterAlive.get() == 0){ + // spawn the last turn of monsters + this.sceneScriptManager.callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs(this.monsterKillCount.get())); + } + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerMiddleLevelChangeTeamNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerMiddleLevelChangeTeamNotify.java new file mode 100644 index 000000000..f778c68aa --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerMiddleLevelChangeTeamNotify.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TowerMiddleLevelChangeTeamNotifyOuterClass; + +public class PacketTowerMiddleLevelChangeTeamNotify extends BasePacket { + + public PacketTowerMiddleLevelChangeTeamNotify() { + super(PacketOpcodes.TowerMiddleLevelChangeTeamNotify); + + TowerMiddleLevelChangeTeamNotifyOuterClass.TowerMiddleLevelChangeTeamNotify proto = + TowerMiddleLevelChangeTeamNotifyOuterClass.TowerMiddleLevelChangeTeamNotify.newBuilder() + .build(); + + this.setData(proto); + } +} From 80db118f72731a64e3371e7b31f419ce5c1357ab Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Fri, 6 May 2022 17:36:13 +0930 Subject: [PATCH 036/312] Add permissionTargeted to applicable commands Change target perm from target.perm to x.perm.others --- .../emu/grasscutter/command/commands/ChangeSceneCommand.java | 2 +- .../java/emu/grasscutter/command/commands/ClearCommand.java | 2 +- src/main/java/emu/grasscutter/command/commands/CoopCommand.java | 2 +- src/main/java/emu/grasscutter/command/commands/DropCommand.java | 2 +- .../emu/grasscutter/command/commands/EnterDungeonCommand.java | 2 +- .../java/emu/grasscutter/command/commands/GiveAllCommand.java | 2 +- .../emu/grasscutter/command/commands/GiveArtifactCommand.java | 2 +- .../java/emu/grasscutter/command/commands/GiveCharCommand.java | 2 +- src/main/java/emu/grasscutter/command/commands/GiveCommand.java | 2 +- .../java/emu/grasscutter/command/commands/GodModeCommand.java | 2 +- src/main/java/emu/grasscutter/command/commands/HealCommand.java | 2 +- .../java/emu/grasscutter/command/commands/KillAllCommand.java | 2 +- .../emu/grasscutter/command/commands/KillCharacterCommand.java | 2 +- .../emu/grasscutter/command/commands/ResetConstCommand.java | 2 +- .../emu/grasscutter/command/commands/ResetShopLimitCommand.java | 2 +- .../emu/grasscutter/command/commands/SendMessageCommand.java | 2 +- .../emu/grasscutter/command/commands/SetFetterLevelCommand.java | 2 +- .../java/emu/grasscutter/command/commands/SetStatsCommand.java | 2 +- .../emu/grasscutter/command/commands/SetWorldLevelCommand.java | 2 +- .../java/emu/grasscutter/command/commands/SpawnCommand.java | 2 +- .../java/emu/grasscutter/command/commands/TalentCommand.java | 2 +- .../emu/grasscutter/command/commands/TeleportAllCommand.java | 2 +- .../java/emu/grasscutter/command/commands/TeleportCommand.java | 2 +- .../java/emu/grasscutter/command/commands/WeatherCommand.java | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java index 594eb27c6..51ddb0c5c 100644 --- a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java @@ -8,7 +8,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "changescene", usage = "changescene ", aliases = {"scene"}, permission = "player.changescene", description = "commands.changescene.description") +@Command(label = "changescene", usage = "changescene ", aliases = {"scene"}, permission = "player.changescene", permissionTargeted = "player.changescene.others", description = "commands.changescene.description") public final class ChangeSceneCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/ClearCommand.java b/src/main/java/emu/grasscutter/command/commands/ClearCommand.java index 38f78e638..ab0ff0eb1 100644 --- a/src/main/java/emu/grasscutter/command/commands/ClearCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ClearCommand.java @@ -14,7 +14,7 @@ import static emu.grasscutter.utils.Language.translate; @Command(label = "clear", usage = "clear ", //Merged /clearartifacts and /clearweapons to /clear [uid] description = "commands.clear.description", - aliases = {"clear"}, permission = "player.clearinv") + aliases = {"clear"}, permission = "player.clearinv", permissionTargeted = "player.clearinv.others") public final class ClearCommand implements CommandHandler { diff --git a/src/main/java/emu/grasscutter/command/commands/CoopCommand.java b/src/main/java/emu/grasscutter/command/commands/CoopCommand.java index cf7d4fc82..a86472978 100644 --- a/src/main/java/emu/grasscutter/command/commands/CoopCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/CoopCommand.java @@ -9,7 +9,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "coop", usage = "coop [host UID]", permission = "server.coop", description = "commands.coop.description") +@Command(label = "coop", usage = "coop [host UID]", permission = "server.coop", permissionTargeted = "server.coop.others", description = "commands.coop.description") public final class CoopCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/DropCommand.java b/src/main/java/emu/grasscutter/command/commands/DropCommand.java index 10c306cad..e2579a7be 100644 --- a/src/main/java/emu/grasscutter/command/commands/DropCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/DropCommand.java @@ -13,7 +13,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "drop", usage = "drop [amount]", aliases = {"d", "dropitem"}, permission = "server.drop", description = "commands.drop.description") +@Command(label = "drop", usage = "drop [amount]", aliases = {"d", "dropitem"}, permission = "server.drop", permissionTargeted = "server.drop.others", description = "commands.drop.description") public final class DropCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java b/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java index 8534c034f..c4e37a93e 100644 --- a/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java @@ -8,7 +8,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "enterdungeon", usage = "enterdungeon ", aliases = {"dungeon"}, permission = "player.enterdungeon", description = "commands.enter_dungeon.description") +@Command(label = "enterdungeon", usage = "enterdungeon ", aliases = {"dungeon"}, permission = "player.enterdungeon", permissionTargeted = "player.enterdungeon.others", description = "commands.enter_dungeon.description") public final class EnterDungeonCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java index c94d67129..6b9104626 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java @@ -15,7 +15,7 @@ import java.util.*; import static emu.grasscutter.utils.Language.translate; -@Command(label = "giveall", usage = "giveall [amount]", aliases = {"givea"}, permission = "player.giveall", threading = true, description = "commands.giveAll.description") +@Command(label = "giveall", usage = "giveall [amount]", aliases = {"givea"}, permission = "player.giveall", permissionTargeted = "player.giveall.others", threading = true, description = "commands.giveAll.description") public final class GiveAllCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java index cf479ed57..25503dbd3 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java @@ -19,7 +19,7 @@ import static java.util.Map.entry; import static emu.grasscutter.utils.Language.translate; -@Command(label = "giveart", usage = "giveart [[,]]... [level]", aliases = {"gart"}, permission = "player.giveart", description = "commands.giveArtifact.description") +@Command(label = "giveart", usage = "giveart [[,]]... [level]", aliases = {"gart"}, permission = "player.giveart", permissionTargeted = "player.giveart.others", description = "commands.giveArtifact.description") public final class GiveArtifactCommand implements CommandHandler { private static final Map> mainPropMap = Map.ofEntries( entry("hp", Map.ofEntries(entry(EquipType.EQUIP_BRACER, 14001))), diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java index 5c6bad0d2..af789f394 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java @@ -12,7 +12,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "givechar", usage = "givechar [level]", aliases = {"givec"}, permission = "player.givechar", description = "commands.giveChar.description") +@Command(label = "givechar", usage = "givechar [level]", aliases = {"givec"}, permission = "player.givechar", permissionTargeted = "player.givechar.others", description = "commands.giveChar.description") public final class GiveCharCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java index 19a9a8d26..fcac3b9bd 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java @@ -17,7 +17,7 @@ import java.util.regex.Pattern; import static emu.grasscutter.utils.Language.translate; @Command(label = "give", usage = "give [amount] [level]", aliases = { - "g", "item", "giveitem"}, permission = "player.give", description = "commands.give.description") + "g", "item", "giveitem"}, permission = "player.give", description = "commands.give.description", permissionTargeted = "player.give.others") public final class GiveCommand implements CommandHandler { Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java is a joke of a proglang that doesn't have raw string literals Pattern refineRegex = Pattern.compile("r(\\d+)"); diff --git a/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java b/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java index bf2a00c9f..98a375838 100644 --- a/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java @@ -8,7 +8,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "godmode", usage = "godmode [on|off|toggle]", permission = "player.godmode", description = "commands.godmode.description") +@Command(label = "godmode", usage = "godmode [on|off|toggle]", permission = "player.godmode", permissionTargeted = "player.godmode.others", description = "commands.godmode.description") public final class GodModeCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/HealCommand.java b/src/main/java/emu/grasscutter/command/commands/HealCommand.java index 440db0a49..78ff14405 100644 --- a/src/main/java/emu/grasscutter/command/commands/HealCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/HealCommand.java @@ -11,7 +11,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "heal", usage = "heal|h", aliases = {"h"}, permission = "player.heal", description = "commands.heal.description") +@Command(label = "heal", usage = "heal|h", aliases = {"h"}, permission = "player.heal", permissionTargeted = "player.heal.others", description = "commands.heal.description") public final class HealCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java b/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java index da9ac7b5e..853395fe6 100644 --- a/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java @@ -12,7 +12,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "killall", usage = "killall [sceneId]", permission = "server.killall", description = "commands.kill.description") +@Command(label = "killall", usage = "killall [sceneId]", permission = "server.killall", permissionTargeted = "server.killall.others", description = "commands.kill.description") public final class KillAllCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java b/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java index 3eda6f7e7..7cff601c4 100644 --- a/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java @@ -13,7 +13,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"}, permission = "player.killcharacter", description = "commands.list.description") +@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"}, permission = "player.killcharacter", permissionTargeted = "player.killcharacter.others", description = "commands.list.description") public final class KillCharacterCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java b/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java index 3a77cee4d..e8229eaf5 100644 --- a/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java @@ -11,7 +11,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; @Command(label = "resetconst", usage = "resetconst [all]", - aliases = {"resetconstellation"}, permission = "player.resetconstellation", description = "commands.resetConst.description") + aliases = {"resetconstellation"}, permission = "player.resetconstellation", permissionTargeted = "player.resetconstellation.others", description = "commands.resetConst.description") public final class ResetConstCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java index 7aa84ff6a..99244b813 100644 --- a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java @@ -9,7 +9,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "resetshop", usage = "resetshop", permission = "server.resetshop", description = "commands.status.description") +@Command(label = "resetshop", usage = "resetshop", permission = "server.resetshop", permissionTargeted = "server.resetshop.others", description = "commands.status.description") public final class ResetShopLimitCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java b/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java index 18d6264db..befbf4f00 100644 --- a/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java @@ -9,7 +9,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; @Command(label = "say", usage = "say ", - aliases = {"sendservmsg", "sendservermessage", "sendmessage"}, permission = "server.sendmessage", description = "commands.sendMessage.description") + aliases = {"sendservmsg", "sendservermessage", "sendmessage"}, permission = "server.sendmessage", permissionTargeted = "server.sendmessage.others", description = "commands.sendMessage.description") public final class SendMessageCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java b/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java index ca5a3cb43..cf356d06b 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java @@ -12,7 +12,7 @@ import emu.grasscutter.server.packet.send.PacketAvatarFetterDataNotify; import static emu.grasscutter.utils.Language.translate; @Command(label = "setfetterlevel", usage = "setfetterlevel ", - aliases = {"setfetterlvl", "setfriendship"}, permission = "player.setfetterlevel", description = "commands.setFetterLevel.description") + aliases = {"setfetterlvl", "setfriendship"}, permission = "player.setfetterlevel", permissionTargeted = "player.setfetterlevel.others", description = "commands.setFetterLevel.description") public final class SetFetterLevelCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java index c7ed78a58..f770ad60b 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java @@ -15,7 +15,7 @@ import emu.grasscutter.utils.Language; import static emu.grasscutter.utils.Language.translate; -@Command(label = "setstats", usage = "setstats|stats ", aliases = {"stats"}, permission = "player.setstats", description = "commands.setStats.description") +@Command(label = "setstats", usage = "setstats|stats ", aliases = {"stats"}, permission = "player.setstats", permissionTargeted = "player.setstats.others", description = "commands.setStats.description") public final class SetStatsCommand implements CommandHandler { static class Stat { String name; diff --git a/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java b/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java index 41b959336..aa773159e 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java @@ -10,7 +10,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; @Command(label = "setworldlevel", usage = "setworldlevel ", - aliases = {"setworldlvl"}, permission = "player.setworldlevel", description = "commands.setWorldLevel.description") + aliases = {"setworldlvl"}, permission = "player.setworldlevel", permissionTargeted = "player.setworldlevel.others", description = "commands.setWorldLevel.description") public final class SetWorldLevelCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java b/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java index 7f0c704c6..897d9ddd2 100644 --- a/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java @@ -22,7 +22,7 @@ import java.util.Random; import static emu.grasscutter.utils.Language.translate; -@Command(label = "spawn", usage = "spawn [amount] [level(monster only)]", permission = "server.spawn", description = "commands.spawn.description") +@Command(label = "spawn", usage = "spawn [amount] [level(monster only)]", permission = "server.spawn", permissionTargeted = "server.spawn.others", description = "commands.spawn.description") public final class SpawnCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/TalentCommand.java b/src/main/java/emu/grasscutter/command/commands/TalentCommand.java index 1540a81f8..ac492b580 100644 --- a/src/main/java/emu/grasscutter/command/commands/TalentCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TalentCommand.java @@ -14,7 +14,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "talent", usage = "talent ", permission = "player.settalent", description = "commands.talent.description") +@Command(label = "talent", usage = "talent ", permission = "player.settalent", permissionTargeted = "player.settalent.others", description = "commands.talent.description") public final class TalentCommand implements CommandHandler { private void setTalentLevel(Player sender, Player player, Avatar avatar, int talentId, int talentLevel) { int oldLevel = avatar.getSkillLevelMap().get(talentId); diff --git a/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java b/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java index 175f69b81..bfa0ac821 100644 --- a/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java @@ -10,7 +10,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "tpall", usage = "tpall", permission = "player.tpall", description = "commands.teleportAll.description") +@Command(label = "tpall", usage = "tpall", permission = "player.tpall", permissionTargeted = "player.tpall.others", description = "commands.teleportAll.description") public final class TeleportAllCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java b/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java index 0d15b55af..8a9fb9948 100644 --- a/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java @@ -10,7 +10,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "teleport", usage = "teleport [scene id]", aliases = {"tp"}, permission = "player.teleport", description = "commands.teleport.description") +@Command(label = "teleport", usage = "teleport [scene id]", aliases = {"tp"}, permission = "player.teleport", permissionTargeted = "player.teleport.others", description = "commands.teleport.description") public final class TeleportCommand implements CommandHandler { private float parseRelative(String input, Float current) { // TODO: Maybe this will be useful elsewhere later diff --git a/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java b/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java index dd0002790..0edbd8482 100644 --- a/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java @@ -11,7 +11,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "weather", usage = "weather [climateId]", aliases = {"w"}, permission = "player.weather", description = "commands.weather.description") +@Command(label = "weather", usage = "weather [climateId]", aliases = {"w"}, permission = "player.weather", permissionTargeted = "player.weather.others", description = "commands.weather.description") public final class WeatherCommand implements CommandHandler { @Override From f08a89711cd23809d4e353c51bad10f52ed2e9df Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Sat, 7 May 2022 13:30:55 +0930 Subject: [PATCH 037/312] Account permission wildcards --- .../java/emu/grasscutter/game/Account.java | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java index 5b8523ec3..7e8baa291 100644 --- a/src/main/java/emu/grasscutter/game/Account.java +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -1,6 +1,7 @@ package emu.grasscutter.game; import dev.morphia.annotations.*; +import emu.grasscutter.Grasscutter; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.utils.Crypto; import emu.grasscutter.utils.Utils; @@ -107,11 +108,41 @@ public class Account { this.permissions.add(permission); return true; } + public static boolean permissionMatchesWildcard(String wildcard, String[] permissionParts) { + String[] wildcardParts = wildcard.split("\\."); + if (permissionParts.length < wildcardParts.length) { // A longer wildcard can never match a shorter permission + return false; + } + for (int i=0; i= (permissionParts.length-1)) { + return true; + } + break; + default: // This layer isn't a wildcard, it needs to match exactly + if (!wildcardParts[i].equals(permissionParts[i])) { + return false; + } + } + } + // At this point the wildcard will have matched every layer, but if it is shorter then the permission then this is not a match at this point (no **). + return (wildcardParts.length == permissionParts.length); + } + public boolean hasPermission(String permission) { - return this.permissions.contains(permission) || - this.permissions.contains("*") || - (this.permissions.contains("player") || this.permissions.contains("player.*")) && permission.startsWith("player.") || - (this.permissions.contains("server") || this.permissions.contains("server.*")) && permission.startsWith("server."); + if (this.permissions.contains(permission) || this.permissions.contains("*")) { + return true; + } + String[] permissionParts = permission.split("\\."); + for (String p : this.permissions) { + if (permissionMatchesWildcard(p, permissionParts)) { + return true; + } + } + return false; } public boolean removePermission(String permission) { From 713b2698d0d163154262f7f7f840217939a3bf40 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Mon, 9 May 2022 15:33:40 +0930 Subject: [PATCH 038/312] Fix perm field order consistency on GiveCommand --- src/main/java/emu/grasscutter/command/commands/GiveCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java index fcac3b9bd..25500f36f 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java @@ -17,7 +17,7 @@ import java.util.regex.Pattern; import static emu.grasscutter.utils.Language.translate; @Command(label = "give", usage = "give [amount] [level]", aliases = { - "g", "item", "giveitem"}, permission = "player.give", description = "commands.give.description", permissionTargeted = "player.give.others") + "g", "item", "giveitem"}, permission = "player.give", permissionTargeted = "player.give.others", description = "commands.give.description") public final class GiveCommand implements CommandHandler { Pattern lvlRegex = Pattern.compile("l(?:vl?)?(\\d+)"); // Java is a joke of a proglang that doesn't have raw string literals Pattern refineRegex = Pattern.compile("r(\\d+)"); From 722e710c7b25ed66dc6b4365cdc573392097d3bf Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Mon, 9 May 2022 17:01:08 +0800 Subject: [PATCH 039/312] Fix #719 --- .../scripts/service/ScriptMonsterSpawnService.java | 4 ++-- .../scripts/service/ScriptMonsterTideService.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java index dda0d4732..0d6baf25d 100644 --- a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java +++ b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java @@ -28,11 +28,11 @@ public class ScriptMonsterSpawnService { onMonsterCreatedListener.add(consumer); } public void addMonsterDeadListener(Consumer consumer){ - onMonsterCreatedListener.add(consumer); + onMonsterDeadListener.add(consumer); } public void onMonsterDead(EntityMonster entityMonster){ - onMonsterCreatedListener.stream().forEach(l -> l.accept(entityMonster)); + onMonsterDeadListener.forEach(l -> l.accept(entityMonster)); } public void spawnMonster(int groupId, SceneMonster monster) { if(monster == null){ diff --git a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java index 117297f15..3cf186188 100644 --- a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java +++ b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java @@ -66,9 +66,9 @@ public class ScriptMonsterTideService { if(this.monsterTideCount.get() > 0){ // add more this.sceneScriptManager.getScriptMonsterSpawnService().spawnMonster(this.currentGroup.id, getNextMonster()); - }else if(this.monsterAlive.get() == 0){ - // spawn the last turn of monsters - this.sceneScriptManager.callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs(this.monsterKillCount.get())); } + // spawn the last turn of monsters + // fix the 5-2 + this.sceneScriptManager.callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs(this.monsterKillCount.get())); } } From ccdc3d1214dcf13dec211ba1465d902e300d68f4 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Mon, 9 May 2022 01:43:32 -0700 Subject: [PATCH 040/312] Only handle motion notify for current entity. --- .../StaminaManager/StaminaManager.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index 72b91c055..c4b094375 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -243,15 +243,16 @@ public class StaminaManager { cachedEntity = entity; MotionInfo motionInfo = moveInfo.getMotionInfo(); MotionState motionState = motionInfo.getState(); - boolean isReliable = moveInfo.getIsReliable(); - Grasscutter.getLogger().trace("" + motionState + "\t" + (isReliable ? "reliable" : "")); - if (isReliable) { - currentState = motionState; - Vector posVector = motionInfo.getPos(); - Position newPos = new Position(posVector.getX(), posVector.getY(), posVector.getZ()); - if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) { - currentCoordinates = newPos; - } + int notifyEntityId = entity.getId(); + int currentAvatarEntityId = session.getPlayer().getTeamManager().getCurrentAvatarEntity().getId(); + if (notifyEntityId != currentAvatarEntityId) { + return; + } + currentState = motionState; + Vector posVector = motionInfo.getPos(); + Position newPos = new Position(posVector.getX(), posVector.getY(), posVector.getZ()); + if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) { + currentCoordinates = newPos; } startSustainedStaminaHandler(); handleImmediateStamina(session, motionInfo, motionState, entity); From 6197ab21cda18caea4aaf4ba8f687ffc2c499c8f Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Mon, 9 May 2022 01:41:00 -0700 Subject: [PATCH 041/312] Exclude macOS junk in case anyone develops on mac. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 35ee889c5..9fa6c9427 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,6 @@ language/ languages/ gacha-mapping.js data/gacha_mappings.js + +# macOS +.DS_Store From eb2b23928bd08f30771e606e8b8ed47ab8a10e78 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Mon, 9 May 2022 17:01:08 +0800 Subject: [PATCH 042/312] Fix #719 --- .../scripts/service/ScriptMonsterSpawnService.java | 4 ++-- .../scripts/service/ScriptMonsterTideService.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java index dda0d4732..0d6baf25d 100644 --- a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java +++ b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java @@ -28,11 +28,11 @@ public class ScriptMonsterSpawnService { onMonsterCreatedListener.add(consumer); } public void addMonsterDeadListener(Consumer consumer){ - onMonsterCreatedListener.add(consumer); + onMonsterDeadListener.add(consumer); } public void onMonsterDead(EntityMonster entityMonster){ - onMonsterCreatedListener.stream().forEach(l -> l.accept(entityMonster)); + onMonsterDeadListener.forEach(l -> l.accept(entityMonster)); } public void spawnMonster(int groupId, SceneMonster monster) { if(monster == null){ diff --git a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java index 117297f15..3cf186188 100644 --- a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java +++ b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java @@ -66,9 +66,9 @@ public class ScriptMonsterTideService { if(this.monsterTideCount.get() > 0){ // add more this.sceneScriptManager.getScriptMonsterSpawnService().spawnMonster(this.currentGroup.id, getNextMonster()); - }else if(this.monsterAlive.get() == 0){ - // spawn the last turn of monsters - this.sceneScriptManager.callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs(this.monsterKillCount.get())); } + // spawn the last turn of monsters + // fix the 5-2 + this.sceneScriptManager.callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs(this.monsterKillCount.get())); } } From 5d917f185f4c1979ff4361865d5cebbb93168904 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Mon, 9 May 2022 03:42:03 -0700 Subject: [PATCH 043/312] Move "if Grasscutter.getConfig().OpenStamina" to the correct place. --- .../StaminaManager/StaminaManager.java | 97 ++++++++++--------- 1 file changed, 49 insertions(+), 48 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index c4b094375..1156993b1 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -190,14 +190,17 @@ public class StaminaManager { // Returns new stamina and sends PlayerPropNotify public int setStamina(GameSession session, String reason, int newStamina) { - // set stamina - player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); - session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); - // notify updated - for (Map.Entry listener : afterUpdateStaminaListeners.entrySet()) { - listener.getValue().onAfterUpdateStamina(reason, newStamina); + if (Grasscutter.getConfig().OpenStamina) { + // set stamina + player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); + session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); + // notify updated + for (Map.Entry listener : afterUpdateStaminaListeners.entrySet()) { + listener.getValue().onAfterUpdateStamina(reason, newStamina); + } + return newStamina; } - return newStamina; + return player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); } // Kills avatar, removes entity and sends notification. @@ -288,50 +291,48 @@ public class StaminaManager { private class SustainedStaminaHandler extends TimerTask { public void run() { - if (Grasscutter.getConfig().OpenStamina) { - boolean moving = isPlayerMoving(); - int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); - int maxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); - if (moving || (currentStamina < maxStamina)) { - Grasscutter.getLogger().trace("Player moving: " + moving + ", stamina full: " + - (currentStamina >= maxStamina) + ", recalculate stamina"); + boolean moving = isPlayerMoving(); + int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + int maxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); + if (moving || (currentStamina < maxStamina)) { + Grasscutter.getLogger().trace("Player moving: " + moving + ", stamina full: " + + (currentStamina >= maxStamina) + ", recalculate stamina"); - Consumption consumption = new Consumption(ConsumptionType.None); - if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { - consumption = getClimbSustainedConsumption(); - } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { - consumption = getSwimSustainedConsumptions(); - } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { - consumption = getRunWalkDashSustainedConsumption(); - } else if (MotionStatesCategorized.get("FLY").contains(currentState)) { - consumption = getFlySustainedConsumption(); - } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { - consumption = getStandSustainedConsumption(); - } + Consumption consumption = new Consumption(ConsumptionType.None); + if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { + consumption = getClimbSustainedConsumption(); + } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { + consumption = getSwimSustainedConsumptions(); + } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { + consumption = getRunWalkDashSustainedConsumption(); + } else if (MotionStatesCategorized.get("FLY").contains(currentState)) { + consumption = getFlySustainedConsumption(); + } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { + consumption = getStandSustainedConsumption(); + } - /* - TODO: Reductions that apply to all motion types: - Elemental Resonance - Wind: -15% - Skills - Diona E: -10% while shield lasts - Barbara E: -12% while lasts - */ - if (cachedSession != null) { - if (consumption.amount < 0) { - staminaRecoverDelay = 0; - } - if (consumption.amount > 0 && consumption.consumptionType != ConsumptionType.POWERED_FLY) { - // For POWERED_FLY recover immediately - things like Amber's gliding exam may require this. - if (staminaRecoverDelay < 10) { - // For others recover after 2 seconds (10 ticks) - as official server does. - staminaRecoverDelay++; - consumption.amount = 0; - Grasscutter.getLogger().trace("[StaminaManager] Delaying recovery: " + staminaRecoverDelay); - } - } - updateStaminaRelative(cachedSession, consumption); + /* + TODO: Reductions that apply to all motion types: + Elemental Resonance + Wind: -15% + Skills + Diona E: -10% while shield lasts + Barbara E: -12% while lasts + */ + if (cachedSession != null) { + if (consumption.amount < 0) { + staminaRecoverDelay = 0; } + if (consumption.amount > 0 && consumption.consumptionType != ConsumptionType.POWERED_FLY) { + // For POWERED_FLY recover immediately - things like Amber's gliding exam may require this. + if (staminaRecoverDelay < 10) { + // For others recover after 2 seconds (10 ticks) - as official server does. + staminaRecoverDelay++; + consumption.amount = 0; + Grasscutter.getLogger().trace("[StaminaManager] Delaying recovery: " + staminaRecoverDelay); + } + } + updateStaminaRelative(cachedSession, consumption); } } previousState = currentState; From 63efef8d056ae71152390f23d94272418097dea1 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Mon, 9 May 2022 20:21:40 +0930 Subject: [PATCH 044/312] Fix /talent saving to level instead of id --- .../java/emu/grasscutter/command/commands/TalentCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/command/commands/TalentCommand.java b/src/main/java/emu/grasscutter/command/commands/TalentCommand.java index ac492b580..40ac11b50 100644 --- a/src/main/java/emu/grasscutter/command/commands/TalentCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TalentCommand.java @@ -24,7 +24,7 @@ public final class TalentCommand implements CommandHandler { } // Upgrade skill - avatar.getSkillLevelMap().put(talentLevel, talentLevel); + avatar.getSkillLevelMap().put(talentId, talentLevel); avatar.save(); // Packet From c432ace5d88344980a49b7e4fdb174531d9b1dad Mon Sep 17 00:00:00 2001 From: Mateoust <46558043+Mateoust@users.noreply.github.com> Date: Mon, 9 May 2022 19:03:34 +0800 Subject: [PATCH 045/312] Updating language files fixed the issue 648 677 fix issue 648 677 --- src/main/resources/languages/en-US.json | 7 +++++++ src/main/resources/languages/pl-PL.json | 9 ++++++++- src/main/resources/languages/zh-CN.json | 11 +++++++++-- src/main/resources/languages/zh-TW.json | 11 +++++++++-- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 4ec17a214..9e7271ea3 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -334,6 +334,13 @@ }, "restart": { "description": "Restarts the current session" + }, + "unlocktower": { + "success": "unlock done", + "description": "Unlock all levels of tower" + }, + "resetshop": { + "description": "reset shop" } } } diff --git a/src/main/resources/languages/pl-PL.json b/src/main/resources/languages/pl-PL.json index e9ff74e25..8f76d8951 100644 --- a/src/main/resources/languages/pl-PL.json +++ b/src/main/resources/languages/pl-PL.json @@ -184,7 +184,7 @@ "account_error": "Konto nie może zostać znalezione." }, "position": { - "success": "Koordynaty: %.3f, %.3f, %.3f\nID sceny: %d" + "success": "Koordynaty: %s, %s, %s\nID sceny: %s" }, "reload": { "reload_start": "Ponowne ładowanie konfiguracji.", @@ -293,6 +293,13 @@ "usage": "Użycie: ", "aliases": "Aliasy: ", "available_commands": "Dostępne komendy: " + }, + "unlocktower": { + "success": "odblokować gotowe", + "description": "Odblokuj głęboką spiralę" + }, + "resetshop": { + "description": "zresetuj sklep" } } } \ No newline at end of file diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 89d1d6a90..f9e750438 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -201,7 +201,7 @@ "description": "给予或移除指定玩家的权限。" }, "position": { - "success": "坐标:%.3f, %.3f, %.3f\n场景ID:%d", + "success": "坐标:%s, %s, %s\n场景ID:%s", "description": "获取所在位置。" }, "reload": { @@ -249,7 +249,7 @@ "setFetterLevel": { "usage": "用法:setfetterlevel ", "range_error": "好感度等级必须在 0 到 10 之间。", - "fetter_set_level": "好感度已设置为 %s 级", + "success": "好感度已设置为 %s 级", "level_error": "无效的好感度等级。", "description": "设置当前角色的好感度等级。" }, @@ -330,6 +330,13 @@ }, "restart": { "description": "重新启动服务器。" + }, + "unlocktower": { + "success": "解锁完成。", + "description": "解锁深境螺旋的所有层" + }, + "resetshop": { + "description": "重置商店时间" } } } diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index 7a7f5c100..9c3c99686 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -184,7 +184,7 @@ "account_error": "The account cannot be found." }, "position": { - "success": "坐標:%.3f, %.3f, %.3f\n場景ID:%d" + "success": "坐標:%s, %s, %s\n場景ID:%s" }, "reload": { "reload_start": "正在重新加載設定檔。", @@ -226,7 +226,7 @@ "setFetterLevel": { "usage": "用法:setfetterlevel ", "range_error": "好感度必須在 0 到 10 之間。", - "fetter_set_level": "好感等級已設定為 %s", + "success": "好感等級已設定為 %s", "level_error": "無效的好感度。" }, "setStats": { @@ -293,6 +293,13 @@ "usage": "用法:", "aliases": "別名:", "available_commands": "可用指令:" + }, + "unlocktower": { + "success": "解鎖完成。", + "description": "解鎖所有級別的深境螺旋" + }, + "resetshop": { + "description": "重置商店時間" } } } From 9a7d78e0b7e4025dcd24437ca28278b08bffc350 Mon Sep 17 00:00:00 2001 From: Mateoust <46558043+Mateoust@users.noreply.github.com> Date: Mon, 9 May 2022 19:05:03 +0800 Subject: [PATCH 046/312] Update translation keys to fix issues 648 677 fix issues 648 677 --- src/main/java/emu/grasscutter/command/CommandMap.java | 6 +++--- .../grasscutter/command/commands/ChangeSceneCommand.java | 5 +++-- .../grasscutter/command/commands/ResetShopLimitCommand.java | 2 +- .../emu/grasscutter/command/commands/SetStatsCommand.java | 2 +- .../java/emu/grasscutter/command/commands/SpawnCommand.java | 6 +++--- .../grasscutter/command/commands/UnlockTowerCommand.java | 4 ++-- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/CommandMap.java b/src/main/java/emu/grasscutter/command/CommandMap.java index a183c6ac3..2ff105b31 100644 --- a/src/main/java/emu/grasscutter/command/CommandMap.java +++ b/src/main/java/emu/grasscutter/command/CommandMap.java @@ -150,7 +150,7 @@ public final class CommandMap { int uid = Integer.parseInt(targetUidStr); targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid); if (targetPlayer == null) { - CommandHandler.sendMessage(player, translate("commands.generic.execution.player_exist_offline_error")); + CommandHandler.sendMessage(player, translate("commands.execution.player_exist_offline_error")); } else { targetPlayerIds.put(playerId, uid); CommandHandler.sendMessage(player, translate("commands.execution.set_target", targetUidStr)); @@ -178,7 +178,7 @@ public final class CommandMap { int uid = Integer.parseInt(arg); targetPlayer = Grasscutter.getGameServer().getPlayerByUid(uid); if (targetPlayer == null) { - CommandHandler.sendMessage(player, translate("commands.generic.execution.player_exist_offline_error")); + CommandHandler.sendMessage(player, translate("commands.execution.player_exist_offline_error")); return; } break; @@ -194,7 +194,7 @@ public final class CommandMap { if (targetPlayerIds.containsKey(playerId)) { targetPlayer = Grasscutter.getGameServer().getPlayerByUid(targetPlayerIds.get(playerId)); // We check every time in case the target goes offline after being targeted if (targetPlayer == null) { - CommandHandler.sendMessage(player, translate("commands.generic.execution.player_exist_offline_error")); + CommandHandler.sendMessage(player, translate("commands.execution.player_exist_offline_error")); return; } } else { diff --git a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java index 51ddb0c5c..59706dd96 100644 --- a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java @@ -31,11 +31,12 @@ public final class ChangeSceneCommand implements CommandHandler { } boolean result = targetPlayer.getWorld().transferPlayerToScene(targetPlayer, sceneId, targetPlayer.getPos()); - CommandHandler.sendMessage(sender, translate("commands.changescene.result", Integer.toString(sceneId))); - if (!result) { CommandHandler.sendMessage(sender, translate("commands.changescene.exists_error")); + return; } + + CommandHandler.sendMessage(sender, translate("commands.changescene.success", Integer.toString(sceneId))); } catch (Exception e) { CommandHandler.sendMessage(sender, translate("commands.execution.argument_error")); } diff --git a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java index 99244b813..bf5e2a4a6 100644 --- a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java @@ -9,7 +9,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "resetshop", usage = "resetshop", permission = "server.resetshop", permissionTargeted = "server.resetshop.others", description = "commands.status.description") +@Command(label = "resetshop", usage = "resetshop", permission = "server.resetshop", permissionTargeted = "server.resetshop.others", description = "commands.resetshop.description") public final class ResetShopLimitCommand implements CommandHandler { @Override diff --git a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java index f770ad60b..a0572d2c2 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java @@ -175,7 +175,7 @@ public final class SetStatsCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { - String syntax = sender == null ? translate("commands.setStats.usage_console") : translate("commands.setStats.ingame"); + String syntax = sender == null ? translate("commands.setStats.usage_console") : translate("commands.setStats.usage_ingame"); String usage = syntax + translate("commands.setStats.help_message"); String statStr; String valueStr; diff --git a/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java b/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java index 897d9ddd2..e3193c638 100644 --- a/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java @@ -46,13 +46,13 @@ public final class SpawnCommand implements CommandHandler { try { amount = Integer.parseInt(args.get(1)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.generic.error.amount")); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.amount")); } // Fallthrough case 1: try { id = Integer.parseInt(args.get(0)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.generic.error.entityId")); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.entityId")); } break; default: @@ -64,7 +64,7 @@ public final class SpawnCommand implements CommandHandler { GadgetData gadgetData = GameData.getGadgetDataMap().get(id); ItemData itemData = GameData.getItemDataMap().get(id); if (monsterData == null && gadgetData == null && itemData == null) { - CommandHandler.sendMessage(sender, translate("commands.generic.error.entityId")); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.entityId")); return; } Scene scene = targetPlayer.getScene(); diff --git a/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java b/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java index e0fce695c..bd7b8bc1f 100644 --- a/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java @@ -10,7 +10,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; @Command(label = "unlocktower", usage = "unlocktower", aliases = {"ut"}, - description = "Unlock all levels of tower", permission = "player.tower") + description = "commands.unlocktower.description", permission = "player.tower") public class UnlockTowerCommand implements CommandHandler { @Override @@ -21,7 +21,7 @@ public class UnlockTowerCommand implements CommandHandler { unlockFloor(sender, sender.getServer().getTowerScheduleManager() .getScheduleFloors()); - CommandHandler.sendMessage(sender, translate("commands.tower.unlock_done")); + CommandHandler.sendMessage(sender, translate("commands.unlocktower.success")); } public void unlockFloor(Player player, List floors){ From faa3cde57547df30ef09906a83f9480624b8b38a Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Tue, 10 May 2022 00:05:01 +0800 Subject: [PATCH 047/312] fix the Monster spawn between stage challenges --- data/TowerSchedule.json | 2 +- .../game/dungeons/DungeonChallenge.java | 28 ++++++++-- .../game/entity/EntityMonster.java | 12 ++-- .../grasscutter/game/tower/TowerManager.java | 2 +- .../scripts/SceneScriptManager.java | 11 +++- .../emu/grasscutter/scripts/ScriptLib.java | 28 ++++++++-- .../scripts/data/SceneTrigger.java | 11 ++++ .../listener/ScriptMonsterListener.java | 8 +++ .../service/ScriptMonsterSpawnService.java | 25 +++++---- .../service/ScriptMonsterTideService.java | 56 ++++++++++++------- 10 files changed, 133 insertions(+), 50 deletions(-) create mode 100644 src/main/java/emu/grasscutter/scripts/listener/ScriptMonsterListener.java diff --git a/data/TowerSchedule.json b/data/TowerSchedule.json index b93100645..e10416a20 100644 --- a/data/TowerSchedule.json +++ b/data/TowerSchedule.json @@ -1,5 +1,5 @@ { - "scheduleId" : 1, + "scheduleId" : 45, "scheduleStartTime" : "2022-05-01T00:00:00+08:00", "nextScheduleChangeTime" : "2022-05-30T00:00:00+08:00" } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java index 2e07f0058..a13f1a611 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonChallenge.java @@ -28,14 +28,20 @@ public class DungeonChallenge { private int challengeId; private boolean success; private boolean progress; - + /** + * has more challenge + */ + private boolean stage; private int score; private int objective = 0; private IntSet rewardedPlayers; - public DungeonChallenge(Scene scene, SceneGroup group) { + public DungeonChallenge(Scene scene, SceneGroup group, int challengeId, int challengeIndex, int objective) { this.scene = scene; this.group = group; + this.challengeId = challengeId; + this.challengeIndex = challengeIndex; + this.objective = objective; this.setRewardedPlayers(new IntOpenHashSet()); } @@ -86,7 +92,15 @@ public class DungeonChallenge { public int getScore() { return score; } - + + public boolean isStage() { + return stage; + } + + public void setStage(boolean stage) { + this.stage = stage; + } + public int getTimeLimit() { return 600; } @@ -112,7 +126,7 @@ public class DungeonChallenge { if (this.isSuccess()) { // Call success script event this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_SUCCESS, null); - + // Settle settle(); } else { @@ -122,8 +136,10 @@ public class DungeonChallenge { private void settle() { getScene().getDungeonSettleObservers().forEach(o -> o.onDungeonSettle(getScene())); - - getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE, new ScriptArgs(this.isSuccess() ? 1 : 0)); + + if(!stage){ + getScene().getScriptManager().callEvent(EventType.EVENT_DUNGEON_SETTLE, new ScriptArgs(this.isSuccess() ? 1 : 0)); + } } public void onMonsterDie(EntityMonster entity) { diff --git a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java index f8a96a808..491aaa3f4 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java @@ -116,14 +116,18 @@ public class EntityMonster extends GameEntity { if (this.getSpawnEntry() != null) { this.getScene().getDeadSpawnedEntities().add(getSpawnEntry()); } + // first set the challenge data + if (getScene().getChallenge() != null && getScene().getChallenge().getGroup().id == this.getGroupId()) { + getScene().getChallenge().onMonsterDie(this); + } if (getScene().getScriptManager().isInit() && this.getGroupId() > 0) { if(getScene().getScriptManager().getScriptMonsterSpawnService() != null){ getScene().getScriptManager().getScriptMonsterSpawnService().onMonsterDead(this); } - getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, null); - } - if (getScene().getChallenge() != null && getScene().getChallenge().getGroup().id == this.getGroupId()) { - getScene().getChallenge().onMonsterDie(this); + // prevent spawn monster after success + if(getScene().getChallenge() != null && getScene().getChallenge().inProgress()){ + getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_DIE, null); + } } } diff --git a/src/main/java/emu/grasscutter/game/tower/TowerManager.java b/src/main/java/emu/grasscutter/game/tower/TowerManager.java index cac848ea6..790efc7f6 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerManager.java @@ -122,7 +122,7 @@ public class TowerManager { if(!hasNextLevel()){ // set up the next floor - recordMap.put(getNextFloorId(), new TowerLevelRecord(getNextFloorId())); + recordMap.putIfAbsent(getNextFloorId(), new TowerLevelRecord(getNextFloorId())); player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(getNextFloorId(), 1)); }else{ player.getSession().send(new PacketTowerCurLevelRecordChangeNotify(currentFloorId, getCurrentLevel())); diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index bc0e896f5..5b86aa298 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -373,6 +373,12 @@ public class SceneScriptManager { new ScriptMonsterTideService(this, group, tideCount, sceneLimit, ordersConfigId); } + public void unloadCurrentMonsterTide(){ + if(this.getScriptMonsterTideService() == null){ + return; + } + this.getScriptMonsterTideService().unload(); + } public void spawnMonstersByConfigId(int configId, int delayTime) { // TODO delay this.scriptMonsterSpawnService.spawnMonster(this.currentGroup.id, this.currentGroup.monsters.get(configId)); @@ -395,12 +401,15 @@ public class SceneScriptManager { if (params != null) { args = CoerceJavaToLua.coerce(params); } - + + ScriptLib.logger.trace("Call Condition Trigger {}", trigger); ret = safetyCall(trigger.condition, condition, args); } if (ret.isboolean() && ret.checkboolean()) { + ScriptLib.logger.trace("Call Action Trigger {}", trigger); LuaValue action = (LuaValue) this.getBindings().get(trigger.action); + // TODO impl the param of SetGroupVariableValueByGroup var arg = new ScriptArgs(); arg.param2 = 100; var args = CoerceJavaToLua.coerce(arg); diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index 0d686dd5a..b7fb5939f 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -147,6 +147,12 @@ public class ScriptLib { return 1; } + // avoid spawn wrong monster + if(getSceneScriptManager().getScene().getChallenge() != null) + if(!getSceneScriptManager().getScene().getChallenge().inProgress() || + getSceneScriptManager().getScene().getChallenge().getGroup().id != groupId){ + return 0; + } this.getSceneScriptManager().spawnMonstersInGroup(group, suite); return 0; @@ -175,13 +181,13 @@ public class ScriptLib { return 0; } - DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(), group); - challenge.setChallengeId(challengeId); - challenge.setChallengeIndex(challengeIndex); - challenge.setObjective(objective); - + DungeonChallenge challenge = new DungeonChallenge(getSceneScriptManager().getScene(), + group, challengeId, challengeIndex, objective); + // set if tower first stage (6-1) + challenge.setStage(getSceneScriptManager().getVariables().getOrDefault("stage", -1) == 0); + getSceneScriptManager().getScene().setChallenge(challenge); - + challenge.start(); return 0; } @@ -336,9 +342,19 @@ public class ScriptLib { logger.debug("[LUA] Call TowerMirrorTeamSetUp with {},{}", team,var1); + getSceneScriptManager().unloadCurrentMonsterTide(); getSceneScriptManager().getScene().getPlayers().get(0).getTowerManager().mirrorTeamSetUp(team-1); return 0; } + public int CreateGadget(LuaTable table){ + logger.debug("[LUA] Call CreateGadget with {}", + printTable(table)); + var configId = table.get("config_id").toint(); + + //TODO + + return 0; + } } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java index a627f67c4..301fdb8e0 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java @@ -21,4 +21,15 @@ public class SceneTrigger { return name.hashCode(); } + @Override + public String toString() { + return "SceneTrigger{" + + "name='" + name + '\'' + + ", config_id=" + config_id + + ", event=" + event + + ", source='" + source + '\'' + + ", condition='" + condition + '\'' + + ", action='" + action + '\'' + + '}'; + } } diff --git a/src/main/java/emu/grasscutter/scripts/listener/ScriptMonsterListener.java b/src/main/java/emu/grasscutter/scripts/listener/ScriptMonsterListener.java new file mode 100644 index 000000000..b3b99fd61 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/listener/ScriptMonsterListener.java @@ -0,0 +1,8 @@ +package emu.grasscutter.scripts.listener; + +import emu.grasscutter.game.entity.EntityMonster; + +public interface ScriptMonsterListener { + + void onNotify(EntityMonster sceneMonster); +} diff --git a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java index 0d6baf25d..fdc4941f2 100644 --- a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java +++ b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterSpawnService.java @@ -8,31 +8,36 @@ import emu.grasscutter.scripts.SceneScriptManager; import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.data.SceneMonster; import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.scripts.listener.ScriptMonsterListener; import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; public class ScriptMonsterSpawnService { private final SceneScriptManager sceneScriptManager; - private final List> onMonsterCreatedListener = new ArrayList<>(); + private final List onMonsterCreatedListener = new ArrayList<>(); - private final List> onMonsterDeadListener = new ArrayList<>(); + private final List onMonsterDeadListener = new ArrayList<>(); public ScriptMonsterSpawnService(SceneScriptManager sceneScriptManager){ this.sceneScriptManager = sceneScriptManager; } - public void addMonsterCreatedListener(Consumer consumer){ - onMonsterCreatedListener.add(consumer); + public void addMonsterCreatedListener(ScriptMonsterListener scriptMonsterListener){ + onMonsterCreatedListener.add(scriptMonsterListener); } - public void addMonsterDeadListener(Consumer consumer){ - onMonsterDeadListener.add(consumer); + public void addMonsterDeadListener(ScriptMonsterListener scriptMonsterListener){ + onMonsterDeadListener.add(scriptMonsterListener); + } + public void removeMonsterCreatedListener(ScriptMonsterListener scriptMonsterListener){ + onMonsterCreatedListener.remove(scriptMonsterListener); + } + public void removeMonsterDeadListener(ScriptMonsterListener scriptMonsterListener){ + onMonsterDeadListener.remove(scriptMonsterListener); } - public void onMonsterDead(EntityMonster entityMonster){ - onMonsterDeadListener.forEach(l -> l.accept(entityMonster)); + onMonsterDeadListener.forEach(l -> l.onNotify(entityMonster)); } public void spawnMonster(int groupId, SceneMonster monster) { if(monster == null){ @@ -64,7 +69,7 @@ public class ScriptMonsterSpawnService { entity.setGroupId(groupId); entity.setConfigId(monster.config_id); - onMonsterCreatedListener.forEach(action -> action.accept(entity)); + onMonsterCreatedListener.forEach(action -> action.onNotify(entity)); sceneScriptManager.getScene().addEntity(entity); diff --git a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java index 3cf186188..cf23aaca9 100644 --- a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java +++ b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java @@ -6,6 +6,7 @@ import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.data.SceneGroup; import emu.grasscutter.scripts.data.SceneMonster; import emu.grasscutter.scripts.data.ScriptArgs; +import emu.grasscutter.scripts.listener.ScriptMonsterListener; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; @@ -19,6 +20,8 @@ public class ScriptMonsterTideService { private final AtomicInteger monsterKillCount; private final int monsterSceneLimit; private final ConcurrentLinkedQueue monsterConfigOrders; + private final OnMonsterCreated onMonsterCreated= new OnMonsterCreated(); + private final OnMonsterDead onMonsterDead= new OnMonsterDead(); public ScriptMonsterTideService(SceneScriptManager sceneScriptManager, SceneGroup group, int tideCount, int monsterSceneLimit, Integer[] ordersConfigId){ @@ -30,18 +33,21 @@ public class ScriptMonsterTideService { this.monsterAlive = new AtomicInteger(0); this.monsterConfigOrders = new ConcurrentLinkedQueue<>(List.of(ordersConfigId)); - this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterCreatedListener(this::onMonsterCreated); - this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterDeadListener(this::onMonsterDead); + this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterCreatedListener(onMonsterCreated); + this.sceneScriptManager.getScriptMonsterSpawnService().addMonsterDeadListener(onMonsterDead); // spawn the first turn for (int i = 0; i < this.monsterSceneLimit; i++) { this.sceneScriptManager.getScriptMonsterSpawnService().spawnMonster(group.id, getNextMonster()); } } - public void onMonsterCreated(EntityMonster entityMonster){ - if(this.monsterSceneLimit > 0){ - this.monsterTideCount.decrementAndGet(); - this.monsterAlive.incrementAndGet(); + public class OnMonsterCreated implements ScriptMonsterListener{ + @Override + public void onNotify(EntityMonster sceneMonster) { + if(monsterSceneLimit > 0){ + monsterAlive.incrementAndGet(); + monsterTideCount.decrementAndGet(); + } } } @@ -54,21 +60,29 @@ public class ScriptMonsterTideService { return currentGroup.monsters.values().stream().findFirst().orElse(null); } - public void onMonsterDead(EntityMonster entityMonster){ - if(this.monsterSceneLimit <= 0){ - return; + public class OnMonsterDead implements ScriptMonsterListener{ + @Override + public void onNotify(EntityMonster sceneMonster) { + if(monsterSceneLimit <= 0){ + return; + } + if(monsterAlive.decrementAndGet() >= monsterSceneLimit) { + // maybe not happen + return; + } + monsterKillCount.incrementAndGet(); + if(monsterTideCount.get() > 0){ + // add more + sceneScriptManager.getScriptMonsterSpawnService().spawnMonster(currentGroup.id, getNextMonster()); + } + // spawn the last turn of monsters + // fix the 5-2 + sceneScriptManager.callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs(monsterKillCount.get())); } - if(this.monsterAlive.decrementAndGet() >= this.monsterSceneLimit) { - // maybe not happen - return; - } - this.monsterKillCount.incrementAndGet(); - if(this.monsterTideCount.get() > 0){ - // add more - this.sceneScriptManager.getScriptMonsterSpawnService().spawnMonster(this.currentGroup.id, getNextMonster()); - } - // spawn the last turn of monsters - // fix the 5-2 - this.sceneScriptManager.callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs(this.monsterKillCount.get())); + } + + public void unload(){ + this.sceneScriptManager.getScriptMonsterSpawnService().removeMonsterCreatedListener(onMonsterCreated); + this.sceneScriptManager.getScriptMonsterSpawnService().removeMonsterDeadListener(onMonsterDead); } } From 66b17cad266bff4b2329c6760efe65efdd9c2a3a Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Tue, 10 May 2022 00:14:50 +0800 Subject: [PATCH 048/312] little fix --- .../scripts/service/ScriptMonsterTideService.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java index 878438190..57d4735ba 100644 --- a/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java +++ b/src/main/java/emu/grasscutter/scripts/service/ScriptMonsterTideService.java @@ -60,18 +60,18 @@ public class ScriptMonsterTideService { return currentGroup.monsters.values().stream().findFirst().orElse(null); } - public class OnMonsterDead implements ScriptMonsterListener{ + public class OnMonsterDead implements ScriptMonsterListener { @Override public void onNotify(EntityMonster sceneMonster) { - if(monsterSceneLimit <= 0){ + if (monsterSceneLimit <= 0) { return; } - if(monsterAlive.decrementAndGet() >= monsterSceneLimit) { + if (monsterAlive.decrementAndGet() >= monsterSceneLimit) { // maybe not happen return; } monsterKillCount.incrementAndGet(); - if(monsterTideCount.get() > 0){ + if (monsterTideCount.get() > 0) { // add more sceneScriptManager.getScriptMonsterSpawnService().spawnMonster(currentGroup.id, getNextMonster()); } @@ -79,9 +79,7 @@ public class ScriptMonsterTideService { // fix the 5-2 sceneScriptManager.callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs(monsterKillCount.get())); } - // spawn the last turn of monsters - // fix the 5-2 - this.sceneScriptManager.callEvent(EventType.EVENT_MONSTER_TIDE_DIE, new ScriptArgs(this.monsterKillCount.get())); + } public void unload(){ From bdc9e483ae514c9dc509afae60840accdc964ccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AD=B1=E5=82=91?= Date: Mon, 9 May 2022 20:44:46 +0800 Subject: [PATCH 049/312] Fixed MissingFormatArgumentException (#727) Format string is `"given_level": "Given %s with level %s %s times to %s"` --- src/main/java/emu/grasscutter/command/commands/GiveCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java index 25500f36f..a60991bad 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java @@ -123,7 +123,7 @@ public final class GiveCommand implements CommandHandler { } else if (itemData.getItemType() == ItemType.ITEM_WEAPON) { CommandHandler.sendMessage(sender, translate("commands.give.given_with_level_and_refinement", Integer.toString(item), Integer.toString(lvl), Integer.toString(refinement), Integer.toString(amount), Integer.toString(targetPlayer.getUid()))); } else { - CommandHandler.sendMessage(sender, translate("commands.give.given_level", Integer.toString(item), Integer.toString(lvl), Integer.toString(amount))); + CommandHandler.sendMessage(sender, translate("commands.give.given_level", Integer.toString(item), Integer.toString(lvl), Integer.toString(amount), Integer.toString(targetPlayer.getUid()))); } } From 19e798a28b6420fa1785d4ead556c0a4de764ac1 Mon Sep 17 00:00:00 2001 From: ImmuState Date: Mon, 9 May 2022 10:03:51 -0700 Subject: [PATCH 050/312] Change ccount delete to delete all referenced items in the database. --- .../command/commands/AccountCommand.java | 21 ++++++++++++-- .../grasscutter/database/DatabaseHelper.java | 29 +++++++++++++++++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/commands/AccountCommand.java b/src/main/java/emu/grasscutter/command/commands/AccountCommand.java index 4b287afa7..a16cc6480 100644 --- a/src/main/java/emu/grasscutter/command/commands/AccountCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/AccountCommand.java @@ -1,8 +1,10 @@ package emu.grasscutter.command.commands; +import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.Account; import emu.grasscutter.game.player.Player; import java.util.List; @@ -54,11 +56,24 @@ public final class AccountCommand implements CommandHandler { } return; case "delete": - if (DatabaseHelper.deleteAccount(username)) { - CommandHandler.sendMessage(null, translate("commands.account.delete")); - } else { + // Get the account we want to delete. + Account toDelete = DatabaseHelper.getAccountByName(username); + + if (toDelete == null) { CommandHandler.sendMessage(null, translate("commands.account.no_account")); + return; } + + // Get the player for the account. + // If that player is currently online, we kick them before proceeding with the deletion. + Player player = Grasscutter.getGameServer().getPlayerByUid(toDelete.getPlayerUid()); + if (player != null) { + player.getSession().close(); + } + + // Finally, we do the actual deletion. + DatabaseHelper.deleteAccount(toDelete); + CommandHandler.sendMessage(null, translate("commands.account.delete")); } } } diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index f63798988..14a3e1c72 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -3,6 +3,8 @@ package emu.grasscutter.database; import java.util.List; import com.mongodb.client.result.DeleteResult; + +import dev.morphia.experimental.MorphiaSession; import dev.morphia.query.FindOptions; import dev.morphia.query.Sort; import dev.morphia.query.experimental.filters.Filters; @@ -95,8 +97,31 @@ public final class DatabaseHelper { return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("playerId", playerId)).first(); } - public static boolean deleteAccount(String username) { - return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("username", username)).delete().getDeletedCount() > 0; + //public static boolean deleteAccount(String username) { + // return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("username", username)).delete().getDeletedCount() > 0; + //} + public static void deleteAccount(Account target) { + // To delete an account, we need to also delete all the other documents in the database that reference the account. + // This should optimally be wrapped inside a transaction, to make sure an error thrown mid-way does not leave the + // database in an inconsistent state, but unfortunately Mongo only supports that when we have a replica set ... + + // Delete mails, gacha records, items and avatars. + DatabaseManager.getDatastore().find(Mail.class).filter(Filters.eq("ownerUid", target.getPlayerUid())).delete(); + DatabaseManager.getDatastore().find(GachaRecord.class).filter(Filters.eq("ownerId", target.getPlayerUid())).delete(); + DatabaseManager.getDatastore().find(GameItem.class).filter(Filters.eq("ownerId", target.getPlayerUid())).delete(); + DatabaseManager.getDatastore().find(Avatar.class).filter(Filters.eq("ownerId", target.getPlayerUid())).delete(); + + // Delete friendships. + // Here, we need to make sure to not only delete the deleted account's friendships, + // but also all friendship entries for that account's friends. + DatabaseManager.getDatastore().find(Friendship.class).filter(Filters.eq("ownerId", target.getPlayerUid())).delete(); + DatabaseManager.getDatastore().find(Friendship.class).filter(Filters.eq("friendId", target.getPlayerUid())).delete(); + + // Delete the player. + DatabaseManager.getDatastore().find(Player.class).filter(Filters.eq("id", target.getPlayerUid())).delete(); + + // Finally, delete the account itself. + DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("id", target.getId())).delete(); } public static List getAllPlayers() { From 1c8207500c15cf0f7a6bdb91b27846f94672fece Mon Sep 17 00:00:00 2001 From: ImmuState Date: Mon, 9 May 2022 10:09:19 -0700 Subject: [PATCH 051/312] Remove commented method and unused import. --- src/main/java/emu/grasscutter/database/DatabaseHelper.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index 14a3e1c72..bfcd006df 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -4,7 +4,6 @@ import java.util.List; import com.mongodb.client.result.DeleteResult; -import dev.morphia.experimental.MorphiaSession; import dev.morphia.query.FindOptions; import dev.morphia.query.Sort; import dev.morphia.query.experimental.filters.Filters; @@ -97,14 +96,11 @@ public final class DatabaseHelper { return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("playerId", playerId)).first(); } - //public static boolean deleteAccount(String username) { - // return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("username", username)).delete().getDeletedCount() > 0; - //} public static void deleteAccount(Account target) { // To delete an account, we need to also delete all the other documents in the database that reference the account. // This should optimally be wrapped inside a transaction, to make sure an error thrown mid-way does not leave the // database in an inconsistent state, but unfortunately Mongo only supports that when we have a replica set ... - + // Delete mails, gacha records, items and avatars. DatabaseManager.getDatastore().find(Mail.class).filter(Filters.eq("ownerUid", target.getPlayerUid())).delete(); DatabaseManager.getDatastore().find(GachaRecord.class).filter(Filters.eq("ownerId", target.getPlayerUid())).delete(); From c2230485c17811928b160ae84c522acd49bd86c4 Mon Sep 17 00:00:00 2001 From: Kinesis Date: Mon, 9 May 2022 23:10:35 +0800 Subject: [PATCH 052/312] fixed error KillCharacter description in the help command --- .../emu/grasscutter/command/commands/KillCharacterCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java b/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java index 7cff601c4..7435c6d5f 100644 --- a/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java @@ -13,7 +13,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"}, permission = "player.killcharacter", permissionTargeted = "player.killcharacter.others", description = "commands.list.description") +@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"}, permission = "player.killcharacter", permissionTargeted = "player.killcharacter.others", description = "commands.killcharacter.description") public final class KillCharacterCommand implements CommandHandler { @Override From f5509d1e7dcad132a78e0d5cc8a68d3b66a59455 Mon Sep 17 00:00:00 2001 From: hatsune-miku Date: Mon, 9 May 2022 17:51:46 -0230 Subject: [PATCH 053/312] Disable falling damage for godmode --- .../server/packet/recv/HandlerCombatInvocationsNotify.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java index 50fca5101..1ce089c26 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java @@ -102,6 +102,10 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { if (cachedLandingSpeed < -28) { damageFactor = 1f; } + // Disable falling damage for players in god mode. + if (session.getPlayer() != null && session.getPlayer().inGodmode()) { + damageFactor = 0; + } float damage = maxHP * damageFactor; float newHP = currentHP - damage; if (newHP < 0) { From 01a33906d36d8603b8b72cd49841aa842ee1c6c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9C=9F=E5=BF=83?= Date: Tue, 10 May 2022 13:13:42 +0800 Subject: [PATCH 054/312] Update build.gradle --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index dd8fa9fa0..4434ed28e 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,7 @@ sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 group = 'xyz.grasscutters' -version = '1.1.0' +version = '1.1.1-dev' sourceCompatibility = 17 targetCompatibility = 17 From d179b5c7dc01ccc8e2a7b43f01a5fb356287febd Mon Sep 17 00:00:00 2001 From: FpguDhk <35809384+341101@users.noreply.github.com> Date: Tue, 10 May 2022 14:15:17 +0800 Subject: [PATCH 055/312] Fix the decision statement of adding map marker. (#763) * Fix the Chinese messy code problem. * Fix the decision statement of adding map marker. --- .../game/managers/MapMarkManager/MapMarksManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMarksManager.java b/src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMarksManager.java index 5249f59d9..d014ce204 100644 --- a/src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMarksManager.java +++ b/src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMarksManager.java @@ -46,7 +46,7 @@ public class MapMarksManager { public boolean addMapMark(MapMark mapMark) { if (mapMarks.size() < mapMarkMaxCount) { - if (!mapMarks.containsKey(mapMark.getPosition())) { + if (!mapMarks.containsKey(getMapMarkKey(mapMark.getPosition()))) { mapMarks.put(getMapMarkKey(mapMark.getPosition()), mapMark); return true; } From 5a37acde2be9aa696e089e5255b5ad74022bd294 Mon Sep 17 00:00:00 2001 From: Secretboy-SMR Date: Mon, 9 May 2022 17:30:51 +0800 Subject: [PATCH 056/312] Fix the following issues: 1. HashMap non-thread-safe issus 2. Fix the same problem in pr621, but use a better implementation Add the following functions: 1. There is now a language cache inside getLanguage to prepare for different languages corresponding to different time zones where the accounts in the server are located --- src/main/java/emu/grasscutter/Config.java | 2 +- src/main/java/emu/grasscutter/Grasscutter.java | 9 +-------- src/main/java/emu/grasscutter/utils/Language.java | 14 +++++++++++--- src/main/java/emu/grasscutter/utils/Utils.java | 9 +++++++++ 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/main/java/emu/grasscutter/Config.java b/src/main/java/emu/grasscutter/Config.java index 6473e2846..4ec16e0d1 100644 --- a/src/main/java/emu/grasscutter/Config.java +++ b/src/main/java/emu/grasscutter/Config.java @@ -22,7 +22,7 @@ public final class Config { public GameServerOptions GameServer = new GameServerOptions(); public DispatchServerOptions DispatchServer = new DispatchServerOptions(); public Locale LocaleLanguage = Locale.getDefault(); - public Locale DefaultLanguage = Locale.ENGLISH; + public Locale DefaultLanguage = Locale.US; public Boolean OpenStamina = true; public GameServerOptions getGameServerOptions() { diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index c593f5f13..f426d41af 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -149,14 +149,7 @@ public final class Grasscutter { public static void loadLanguage() { var locale = config.LocaleLanguage; - var languageTag = locale.toLanguageTag(); - - if (languageTag.equals("und")) { - Grasscutter.getLogger().error("Illegal locale language, using 'en-US' instead."); - language = Language.getLanguage("en-US"); - } else { - language = Language.getLanguage(languageTag); - } + language = Language.getLanguage(Utils.getLanguageCode(locale)); } public static void saveConfig() { diff --git a/src/main/java/emu/grasscutter/utils/Language.java b/src/main/java/emu/grasscutter/utils/Language.java index 70e32e658..04bd352f7 100644 --- a/src/main/java/emu/grasscutter/utils/Language.java +++ b/src/main/java/emu/grasscutter/utils/Language.java @@ -6,12 +6,13 @@ import emu.grasscutter.Grasscutter; import javax.annotation.Nullable; import java.io.InputStream; -import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; import java.util.Map; public final class Language { private final JsonObject languageData; - private final Map cachedTranslations = new HashMap<>(); + private final Map cachedTranslations = new ConcurrentHashMap<>(); + private static final Map cachedLanguages = new ConcurrentHashMap<>(); /** * Creates a language instance from a code. @@ -19,7 +20,13 @@ public final class Language { * @return A language instance. */ public static Language getLanguage(String langCode) { - return new Language(langCode + ".json", Grasscutter.getConfig().DefaultLanguage.toLanguageTag() + ".json"); + if (cachedLanguages.containsKey(langCode)) { + return cachedLanguages.get(langCode); + } + + var languageInst = new Language(langCode + ".json", Utils.getLanguageCode(Grasscutter.getConfig().DefaultLanguage) + ".json"); + cachedLanguages.put(langCode, languageInst); + return languageInst; } /** @@ -42,6 +49,7 @@ public final class Language { /** * Reads a file and creates a language instance. * @param fileName The name of the language file. + * @param fallback The name of the fallback language file. */ private Language(String fileName, String fallback) { @Nullable JsonObject languageData = null; diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index 1d79c496e..764993255 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -9,6 +9,7 @@ import java.time.temporal.TemporalAdjusters; import java.util.HashMap; import java.util.Map; import java.util.Random; +import java.util.Locale; import emu.grasscutter.Config; import emu.grasscutter.Grasscutter; @@ -306,4 +307,12 @@ public final class Utils { return map; } + + /** + * get language code from Locale + */ + public static String getLanguageCode(Locale locale) { + return String.format("%s-%s", locale.getLanguage(), locale.getCountry()); + } + } From a601e13d8016f842710c3ea88fa0679b45e5ab65 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Tue, 10 May 2022 01:41:20 -0700 Subject: [PATCH 057/312] No more fallen to death in god mode --- .../server/packet/recv/HandlerCombatInvocationsNotify.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java index 1ce089c26..04604ddc1 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java @@ -77,6 +77,9 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { } private void handleFallOnGround(GameSession session, GameEntity entity, MotionState motionState) { + if (session.getPlayer().inGodmode()) { + return; + } // People have reported that after plunge attack (client sends a FIGHT instead of FALL_ON_GROUND) they will die // if they talk to an NPC (this is when the client sends a FALL_ON_GROUND) without jumping again. // A dirty patch: if not received immediately after MOTION_LAND_SPEED, discard this packet. From c424d15b32f1f55f09d10aa317a0f84fc3542b48 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Tue, 10 May 2022 01:48:27 -0700 Subject: [PATCH 058/312] Remove previous fix for falling to death in godmode This fix is more efficient --- .../server/packet/recv/HandlerCombatInvocationsNotify.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java index 04604ddc1..95171bec8 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java @@ -105,10 +105,6 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { if (cachedLandingSpeed < -28) { damageFactor = 1f; } - // Disable falling damage for players in god mode. - if (session.getPlayer() != null && session.getPlayer().inGodmode()) { - damageFactor = 0; - } float damage = maxHP * damageFactor; float newHP = currentHP - damage; if (newHP < 0) { From 0ea0eab6b615aed4ebbf448524471b3833e9c8d9 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Tue, 10 May 2022 01:42:23 -0700 Subject: [PATCH 059/312] Stamina will be set to full if Stamina is disabled. --- .../StaminaManager/StaminaManager.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index 1156993b1..937ba3b3f 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -190,17 +190,17 @@ public class StaminaManager { // Returns new stamina and sends PlayerPropNotify public int setStamina(GameSession session, String reason, int newStamina) { - if (Grasscutter.getConfig().OpenStamina) { - // set stamina - player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); - session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); - // notify updated - for (Map.Entry listener : afterUpdateStaminaListeners.entrySet()) { - listener.getValue().onAfterUpdateStamina(reason, newStamina); - } - return newStamina; + if (!Grasscutter.getConfig().OpenStamina) { + newStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); } - return player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + // set stamina + player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); + session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); + // notify updated + for (Map.Entry listener : afterUpdateStaminaListeners.entrySet()) { + listener.getValue().onAfterUpdateStamina(reason, newStamina); + } + return newStamina; } // Kills avatar, removes entity and sends notification. From fb3c3b71c83ca8045e9d2013f54616aa73a45d3d Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Tue, 10 May 2022 02:03:18 -0700 Subject: [PATCH 060/312] Temporarily set statue auto use to 1 and 100%. --- .../java/emu/grasscutter/game/managers/SotSManager.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/game/managers/SotSManager.java b/src/main/java/emu/grasscutter/game/managers/SotSManager.java index ed67c6a62..564663662 100644 --- a/src/main/java/emu/grasscutter/game/managers/SotSManager.java +++ b/src/main/java/emu/grasscutter/game/managers/SotSManager.java @@ -93,9 +93,14 @@ public class SotSManager { } public void refillSpringVolume() { - // TODO: max spring volume depends on level of the statues in Mondstadt and Liyue. + // 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 player.setProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME, 8500000); + // Temporary: Auto enable 100% statue recovery until we can adjust statue settings in game + // TODO: remove + player.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, 100); + player.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1); long now = System.currentTimeMillis() / 1000; long secondsSinceLastUsed = now - player.getSpringLastUsed(); From f26fe213a249beabe5d0ba5f865b855d7d510c4b Mon Sep 17 00:00:00 2001 From: Bi Jiakai Date: Tue, 10 May 2022 17:17:54 +0800 Subject: [PATCH 061/312] Fixed account delete can not delete all related data (#767) --- .../grasscutter/database/DatabaseHelper.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index bfcd006df..8f1de0bb9 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -16,6 +16,8 @@ import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.player.Player; +import static com.mongodb.client.model.Filters.eq; + public final class DatabaseHelper { public static Account createAccount(String username) { return createAccountWithId(username, 0); @@ -101,17 +103,20 @@ public final class DatabaseHelper { // This should optimally be wrapped inside a transaction, to make sure an error thrown mid-way does not leave the // database in an inconsistent state, but unfortunately Mongo only supports that when we have a replica set ... - // Delete mails, gacha records, items and avatars. - DatabaseManager.getDatastore().find(Mail.class).filter(Filters.eq("ownerUid", target.getPlayerUid())).delete(); - DatabaseManager.getDatastore().find(GachaRecord.class).filter(Filters.eq("ownerId", target.getPlayerUid())).delete(); - DatabaseManager.getDatastore().find(GameItem.class).filter(Filters.eq("ownerId", target.getPlayerUid())).delete(); - DatabaseManager.getDatastore().find(Avatar.class).filter(Filters.eq("ownerId", target.getPlayerUid())).delete(); + // Delete Mail.class data + DatabaseManager.getDatabase().getCollection("mail").deleteMany(eq("ownerUid", target.getPlayerUid())); + // Delete Avatar.class data + DatabaseManager.getDatabase().getCollection("avatars").deleteMany(eq("ownerId", target.getPlayerUid())); + // Delete GachaRecord.class data + DatabaseManager.getDatabase().getCollection("gachas").deleteMany(eq("ownerId", target.getPlayerUid())); + // Delete GameItem.class data + DatabaseManager.getDatabase().getCollection("items").deleteMany(eq("ownerId", target.getPlayerUid())); // Delete friendships. // Here, we need to make sure to not only delete the deleted account's friendships, // but also all friendship entries for that account's friends. - DatabaseManager.getDatastore().find(Friendship.class).filter(Filters.eq("ownerId", target.getPlayerUid())).delete(); - DatabaseManager.getDatastore().find(Friendship.class).filter(Filters.eq("friendId", target.getPlayerUid())).delete(); + DatabaseManager.getDatabase().getCollection("friendships").deleteMany(eq("ownerId", target.getPlayerUid())); + DatabaseManager.getDatabase().getCollection("friendships").deleteMany(eq("friendId", target.getPlayerUid())); // Delete the player. DatabaseManager.getDatastore().find(Player.class).filter(Filters.eq("id", target.getPlayerUid())).delete(); From 7efbdbf1ed5ae3685f46d68e8110b3b28b73be66 Mon Sep 17 00:00:00 2001 From: tester233 <105267106+tester233@users.noreply.github.com> Date: Tue, 10 May 2022 17:01:22 +0800 Subject: [PATCH 062/312] Update zh-CN.json & fix typo --- src/main/resources/languages/zh-CN.json | 208 ++++++++++++------------ 1 file changed, 106 insertions(+), 102 deletions(-) diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index f9e750438..5a773538d 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -2,47 +2,47 @@ "messages": { "game": { "port_bind": "游戏服务器已在端口 %s 上启动", - "connect": "客户端已连接至 %s", + "connect": "客户端 %s 已连接", "disconnect": "客户端 %s 已断开连接", "game_update_error": "游戏更新时发生错误", "command_error": "命令发生错误:" }, "dispatch": { "port_bind": "[Dispatch] 服务器已在端口 %s 上启动", - "request": "[Dispatch] 客户端 %s 请求: %s %s", + "request": "[Dispatch] 客户端 %s 请求:%s %s", "keystore": { "general_error": "[Dispatch] 加载 keystore 文件时发生错误!", - "password_error": "[Dispatch] 加载 keystore 失败。正在尝试使用预设的 keystore 密码...", + "password_error": "[Dispatch] 加载 keystore 失败。正在尝试使用默认密码...", "no_keystore_error": "[Dispatch] 未找到 SSL 证书!已降级到 HTTP 服务器", "default_password": "[Dispatch] 默认的 keystore 密码加载成功。请考虑将 config.json 的默认密码设置为 123456" }, "no_commands_error": "此命令不适用于 Dispatch-only 模式", - "unhandled_request_error": "[Dispatch] 潜在的未处理请求 %s 请求:%s", + "unhandled_request_error": "[Dispatch] 潜在的未处理请求:%s %s", "account": { "login_attempt": "[Dispatch] 客户端 %s 正在尝试登录", "login_success": "[Dispatch] 客户端 %s 已登录,UID为 %s", - "login_token_attempt": "[Dispatch] 客户端 %s 正在尝试使用令牌登录", - "login_token_error": "[Dispatch] 客户端 %s 使用令牌登录失败", - "login_token_success": "[Dispatch] 客户端 %s 已通过令牌登录,UID为 %s", - "combo_token_success": "[Dispatch] 客户端 %s 交换令牌成功", - "combo_token_error": "[Dispatch] 客户端 %s 交换令牌失败", + "login_token_attempt": "[Dispatch] 客户端 %s 正在尝试使用 token 登录", + "login_token_error": "[Dispatch] 客户端 %s 使用 token 登录失败", + "login_token_success": "[Dispatch] 客户端 %s 已通过 token 登录,UID为 %s", + "combo_token_success": "[Dispatch] 客户端 %s 交换 token 成功", + "combo_token_error": "[Dispatch] 客户端 %s 交换 token 失败", "account_login_create_success": "[Dispatch] 客户端 %s 登录失败: 已注册UID为 %s 的账号", "account_login_create_error": "[Dispatch] 客户端 %s 登录失败:账号创建失败。", "account_login_exist_error": "[Dispatch] 客户端 %s 登录失败:账号不存在", "account_cache_error": "游戏账号缓存信息错误", - "session_key_error": "交换秘钥不符。", + "session_key_error": "会话密钥错误。", "username_error": "未找到此用户名。", "username_create_error": "未找到用户名,建立连接失败。" } }, "status": { - "free_software": "Grasscutter 是免费开源软件,遵循AGPL-3.0 license。如果您是付费购买的,那您已经被骗了。项目地址:Github:https://github.com/Grasscutters/Grasscutter", + "free_software": "Grasscutter 是免费开源软件,遵循 AGPL-3.0 license。如果你是付费购买的,那你已经被骗了。项目地址:https://github.com/Grasscutters/Grasscutter", "starting": "正在启动 Grasscutter...", "shutdown": "正在关闭...", "done": "加载完成!输入 \"help\" 查看命令列表", "error": "发生了一个错误。", "welcome": "欢迎使用 Grasscutter!珍惜这段美妙的旅途吧!", - "run_mode_error": "无效的服务器运行模式: %s。", + "run_mode_error": "无效的服务器运行模式:%s。", "run_mode_help": "服务器运行模式必须为 HYBRID、DISPATCH_ONLY 或 GAME_ONLY。Grasscutter 启动失败...", "create_resources": "正在创建 resources 目录...", "resources_error": "请将 BinOutput 和 ExcelBinOutput 复制到 resources 目录。" @@ -52,18 +52,19 @@ "generic": { "not_specified": "没有指定命令。", "unknown_command": "未知的命令:%s", - "permission_error": "哼哼哼!您没有执行此命令的权限!请联系服务器管理员解决!", + "permission_error": "哼哼哼!你没有执行此命令的权限!请联系服务器管理员解决!", "console_execute_error": "此命令只能在服务器控制台执行呐~", "player_execute_error": "此命令只能在游戏内执行哦~", "command_exist_error": "这条命令……好像找不到呢?。", + "no_description_specified": "没有指定说明", "invalid": { "amount": "无效的数量.", "artifactId": "无效的圣遗物ID。", "avatarId": "无效的角色ID。", - "avatarLevel": "无效的角色等級。", + "avatarLevel": "无效的角色等级。", "entityId": "无效的实体ID。", "itemId": "无效的物品ID。", - "itemLevel": "无效的物品等級。", + "itemLevel": "无效的物品等级。", "itemRefinement": "无效的物品精炼等级。", "playerId": "无效的玩家ID。", "uid": "无效的UID。" @@ -71,16 +72,16 @@ }, "execution": { "uid_error": "无效的UID。", - "player_exist_error": "用户不存在。", + "player_exist_error": "玩家不存在。", "player_offline_error": "玩家已离线。", "item_id_error": "无效的物品ID。.", "item_player_exist_error": "无效的物品/玩家UID。", "entity_id_error": "无效的实体ID。", "player_exist_offline_error": "玩家不存在或已离线。", "argument_error": "无效的参数。", - "clear_target": "目标已清除.", + "clear_target": "目标已清除。", "set_target": "随后的的命令都会以@%s为预设。", - "need_target": "此命令需要一个目标 UID。添加 <@UID> 参数或使用 /target @UID 来设定持久目标。" + "need_target": "此命令需要一个目标 UID。添加 <@UID> 参数或使用 /target @UID 来指定默认目标。" }, "status": { "enabled": "已启用", @@ -89,13 +90,13 @@ "success": "成功" }, "account": { - "modify": "修改使用者账号", + "modify": "修改用户账号", "invalid": "无效的UID。", "exists": "账号已存在。", - "create": "已建立账号,UID 为 %s 。", + "create": "已创建账号,UID 为 %s 。", "delete": "账号已刪除。", "no_account": "账号不存在。", - "command_usage": "用法:account [uid]", + "command_usage": "用法:account <用户名> [uid]", "description": "创建或删除账号。" }, "broadcast": { @@ -104,72 +105,72 @@ "description": "向所有玩家发送公告。" }, "changescene": { - "usage": "用法:changescene ", - "already_in_scene": "你已经在这个秘境中了。", - "success": "已切换至秘境 %s.", - "exists_error": "此秘境不存在。", - "description": "切换指定秘境。" + "usage": "用法:changescene <场景ID>", + "already_in_scene": "你已经在这个场景中了。", + "success": "已切换至场景 %s。", + "exists_error": "此场景不存在。", + "description": "切换指定场景。" }, "clear": { - "command_usage": "用法: clear ", - "weapons": "已将 %s 的武器清空。", - "artifacts": "已将 %s 的圣遗物清空。", - "materials": "已将 %s 的材料清空。", - "furniture": "已将 %s 的尘歌壶家具清空。", - "displays": "已清除 %s 的显示。", - "virtuals": "已将 %s 的所有货币和经验值清空。", - "everything": "已将 %s 的所有物品清空。", - "description": "从您的背包中删除所有未装备且已解锁的物品,包括稀有物品。" + "command_usage": "用法: clear \nall: 所有, wp: 武器, art: 圣遗物, mat: 材料", + "weapons": "已清除 %s 的武器。", + "artifacts": "已清除 %s 的圣遗物。", + "materials": "已清除 %s 的材料。", + "furniture": "已清除 %s 的尘歌壶家具。", + "displays": "已清空 %s 的屏幕。", + "virtuals": "已清除 %s 的所有货币和经验值。", + "everything": "已清除 %s 的所有物品。", + "description": "从你的背包中删除所有未装备且已解锁的物品,包括稀有物品。" }, "coop": { - "usage": "用法:coop ", - "success": "已强制召唤 %s 到 %s的世界", - "description": "强制召唤指定用户到他人的世界。" + "usage": "用法:coop <玩家ID> <目标玩家ID>", + "success": "已强制传送 %s 到 %s 的世界", + "description": "强制传送指定用户到他人的世界。" }, "enter_dungeon": { - "usage": "用法:enterdungeon ", + "usage": "用法:enterdungeon <秘境ID>", "changed": "已进入秘境 %s", "not_found_error": "此秘境不存在。", "in_dungeon_error": "你已经在秘境中了。", "description": "进入指定秘境。" }, "giveAll": { - "usage": "用法:giveall [player] [amount]", + "usage": "用法:giveall [玩家] [数量]", "started": "正在给予全部物品...", - "success": "已给予全部物品。", + "success": "已给予 %s 全部物品。", "invalid_amount_or_playerId": "无效的数量/玩家ID。", "description": "给予所有物品。" }, "giveArtifact": { - "usage": "用法:giveart|gart [player] [[,]]... [level]", + "usage": "用法:giveart|gart [玩家] <圣遗物ID> <主词条ID> [<副词条ID>[,<强化次数>]]... [等级]", "id_error": "无效的圣遗物ID。", "success": "已将 %s 给予 %s。", "description": "给予指定圣遗物。" }, "giveChar": { - "usage": "用法:givechar [amount]", - "given": "给予角色 %s 等级 %s 向UID %s.", + "usage": "用法:givechar <玩家> <角色ID|角色名> [数量]", + "given": "已将角色 %s (等级 %s) 给与 %s。", "invalid_avatar_id": "无效的角色ID。", - "invalid_avatar_level": "无效的角色等級。.", + "invalid_avatar_level": "无效的角色等级。.", "invalid_avatar_or_player_id": "无效的角色ID/玩家ID。", "description": "给予指定角色。" }, "give": { - "usage": "用法:give [amount] [level] [refinement]", - "refinement_only_applicable_weapons": "精炼等阶参数仅在给予武器时可用", - "refinement_must_between_1_and_5": "精炼等阶必须在 1 到 5 之间。", + "usage": "用法:give <玩家> <物品ID|物品名> [数量] [等级] [精炼等级]", + "refinement_only_applicable_weapons": "只有武器可以设置精炼等级。", + "refinement_must_between_1_and_5": "精炼等级必须在 1 到 5 之间。", "given": "已将 %s 个 %s 给予 %s。", - "given_with_level_and_refinement": "已将 %s [等級%s, 精炼%s] %s个给予 %s", - "given_level": "已将 %s 等级 %s %s 个给予UID %s", + "given_with_level_and_refinement": "已将 %s (等级 %s, 精炼 %s) %s 个给予 %s", + "given_level": "已将 %s (等级 %s) %s 个给予 %s", "description": "给予指定物品。" }, "godmode": { - "success": "上帝模式已被设置为 %s 。 [用户:%s]", + "success": "%s 的无敌模式已被设置为 %s。", "description": "防止你受到伤害。" }, "heal": { - "success": "所有角色已被治疗。", - "description": "治疗所选队伍的角色。" + "success": "已经治疗所有角色。", + "description": "治疗场上队伍的角色。" }, "kick": { "player_kick_player": "玩家 [%s:%s] 已将 [%s:%s] 踢出", @@ -177,28 +178,28 @@ "description": "从服务器内踢出指定玩家。" }, "kill": { - "usage": "用法:killall [playerUid] [sceneId]", + "usage": "用法:killall [玩家UID] [场景ID]", "scene_not_found_in_player_world": "未在玩家世界中找到此场景", - "kill_monsters_in_scene": "已杀死 %s 个怪物。 [场景ID: %s]", + "kill_monsters_in_scene": "已杀死场景 %s 中的 %s 个怪物。", "description": "杀死所有怪物" }, "killCharacter": { - "usage": "用法:/killcharacter [playerId]", - "success": "已杀死 %s 目前使用的角色。", - "description": "杀死目前使用的角色" + "usage": "用法:/killcharacter [玩家ID]", + "success": "已杀死 %s 当前角色。", + "description": "杀死当前角色" }, "list": { "success": "目前在线人数:%s", "description": "查看所有玩家" }, "permission": { - "usage": "用法:permission ", - "add": "已设置权限。", + "usage": "用法:permission <用户名> <权限>", + "add": "权限已添加。", "has_error": "此玩家已拥有此权限!", "remove": "权限已移除。", "not_have_error": "此玩家未拥有权限!", - "account_error": "账号不存在!", - "description": "给予或移除指定玩家的权限。" + "account_error": "账号不存在。", + "description": "添加或移除指定玩家的权限。" }, "position": { "success": "坐标:%s, %s, %s\n场景ID:%s", @@ -206,74 +207,74 @@ }, "reload": { "reload_start": "正在重载配置文件和数据。", - "reload_done": "重载完毕。", + "reload_done": "重载完成。", "description": "重载配置文件和数据。" }, "resetConst": { "reset_all": "重置所有角色的命座。", - "success": "已重置 %s 的命座,重新登录后将会生效。", + "success": "已重置 %s 的命座,重新登录后生效。", "description": "重置当前角色的命之座,执行命令后需重新登录以生效。" }, "resetShopLimit": { - "usage": "用法:/resetshop ", + "usage": "用法:/resetshop <玩家ID>", "description": "重置所选玩家的商店刷新时间。" }, "sendMail": { - "usage": "用法:give [player] [amount]", - "user_not_exist": "ID '%s' 的使用者不存在。", - "start_composition": "发送邮件流程。\n请使用`/send <标题>`前进到下一步。\n你可以在任何时间使用`/sendmail stop`来停止发送。", + "usage": "用法:give [玩家] <物品ID|物品名称> [数量]", + "user_not_exist": "ID '%s' 的用户不存在。", + "start_composition": "发送邮件流程。\n请使用`/sendmail <标题>`前进到下一步。\n你可以在任何时间使用`/sendmail stop`来停止发送。", "templates": "邮件模板尚未实装...", - "invalid_arguments": "无效的参数。\n指令使用方法 `/sendmail [templateId]`", + "invalid_arguments": "无效的参数。\n指令使用方法 `/sendmail <用户ID|all|help> [模板ID]`", "send_cancel": "取消发送邮件", "send_done": "已将邮件发送给 %s!", "send_all_done": "邮件已发送给所有人!", "not_composition_end": "现在邮件发送未到最后阶段。\n请使用 `/sendmail %s` 继续发送邮件,或使用 `/sendmail stop` 来停止发送邮件。", "please_use": "请使用 `/sendmail %s`", - "set_title": "成功将邮件标题设置为 '%s'。\n使用 '/sendmail ' 来设置邮件内容。", - "set_contents": "成功将'%s'设置为邮件内容。\n使用 '/sendmail <发件人>' 来设置发件人。", - "set_message_sender": "发件人已设置为 '%s'。\n使用 '/sendmail [amount] [level]' 来添加附件。", - "send": "已添加 %s 个 %s (等級为 %s) 邮件附件。\n如果没有要继续添加的道具请使用 `/sendmail finish` 来完成邮件发送。", + "set_title": "成功将邮件标题设置为 '%s'。\n使用 '/sendmail <正文>' 来设置邮件内容。", + "set_contents": "成功将邮件内容设置为 '%s'。\n使用 '/sendmail <发件人>' 来设置发件人。", + "set_message_sender": "发件人已设置为 '%s'。\n使用 '/sendmail <物品ID|物品名称|finish> [数量] [等级]' 来添加附件。", + "send": "已添加 %s 个 %s (等级 %s) 邮件附件。\n如果没有要继续添加的附件请使用 `/sendmail finish` 来发送邮件。", "invalid_arguments_please_use": "错误的参数 \n请使用 `/sendmail %s`", "title": "<标题>", "message": "<正文>", "sender": "<发件人>", - "arguments": " [数量] [等级]", + "arguments": "<物品ID|物品名称|finish> [数量] [等级]", "error": "错误:无效的编写阶段 %s。需要 StackTrace 请查看服务器控制台。", - "description": "向指定用户发送邮件。 此命令的用法可根据附加的参数而变化。" + "description": "向指定用户发送邮件。此命令的用法可根据附加的参数而变化。" }, "sendMessage": { - "usage": "用法:sendmessage ", + "usage": "用法:sendmessage <玩家> <消息>", "success": "消息已发送。", "description": "向指定玩家发送消息" }, "setFetterLevel": { - "usage": "用法:setfetterlevel ", + "usage": "用法:setfetterlevel <好感度等级>", "range_error": "好感度等级必须在 0 到 10 之间。", "success": "好感度已设置为 %s 级", "level_error": "无效的好感度等级。", "description": "设置当前角色的好感度等级。" }, "setStats": { - "usage_console": "用法:setstats|stats @ ", - "usage_ingame": "用法:setstats|stats [@UID] ", - "help_message": "\n\t可使用的数据类型:hp (生命值)| maxhp (最大生命值) | def(防御力) | atk (攻击力)| em (元素精通) | er (元素充能效率) | crate(暴击率) | cdmg (暴击伤害)| cdr (冷却缩减) | heal(治疗加成)| heali (受治疗加成)| shield (护盾强效)| defi (无视防御)\n\t(cont.) 元素伤害:epyro (火) | ecryo (冰) | ehydro (水) | egeo (岩) | edendro (草) | eelectro (雷) | ephys (物理)(cont.) 元素抗性:respyro (火) | rescryo (冰) | reshydro (水) | resgeo (岩) | resdendro (草) | reselectro (雷) | resphys (物理)\n", - "value_error": "无效的数据值。", + "usage_console": "用法:setstats|stats @ <属性> <数值>", + "usage_ingame": "用法:setstats|stats [@UID] <属性> <数值>", + "help_message": "\n\t可更改的属性列表:hp (生命值)| maxhp (最大生命值) | def(防御力) | atk (攻击力)| em (元素精通) | er (元素充能效率) | crate(暴击率) | cdmg (暴击伤害)| cdr (冷却缩减) | heal(治疗加成)| heali (受治疗加成)| shield (护盾强效)| defi (无视防御)\n\t(续) 元素增伤:epyro (火) | ecryo (冰) | ehydro (水) | egeo (岩) | edendro (草) | eelectro (雷) | ephys (物理)\n\t(续) 元素抗性:respyro (火) | rescryo (冰) | reshydro (水) | resgeo (岩) | resdendro (草) | reselectro (雷) | resphys (物理)\n", + "value_error": "无效的属性值。", "uid_error": "无效的UID。", "player_error": "玩家不存在或已离线。", - "set_self": "%s 已经设置为 %s。", - "set_for_uid": "%s 的使用者 %s 更改为 %s。", - "set_max_hp": "最大生命值更改为 %s。", + "set_self": "%s 已设为 %s。", + "set_for_uid": "将 %s (来自 %s) 设置为 %s。", + "set_max_hp": "最大生命值已设为 %s。", "description": "设置当前角色的属性。" }, "setWorldLevel": { - "usage": "用法:setworldlevel ", + "usage": "用法:setworldlevel <等级>", "value_error": "世界等级必须设置在0-8之间。", - "success": "已将世界等级设为%s。", + "success": "已将世界等级设为 %s。", "invalid_world_level": "无效的世界等级。", "description": "设置世界等级,执行命令后需重新登录以生效。" }, "spawn": { - "usage": "用法:spawn [amount] [level(仅限怪物]", + "usage": "用法:spawn <实体ID> [数量] [等级(仅怪物)]", "success": "已生成 %s 个 %s。", "description": "在你附近生成一个生物。" }, @@ -282,14 +283,14 @@ "description": "停止服务器" }, "talent": { - "usage_1": "设置天赋等级:/talent set ", - "usage_2": "另一种设置天赋等级的命令使用方法:/talent ", - "usage_3": "获取天赋ID指令用法:/talent getid", + "usage_1": "设置天赋等级:/talent set <天赋ID> <数值>", + "usage_2": "另一种设置天赋等级的方法:/talent <数值>", + "usage_3": "获取天赋ID:/talent getid", "lower_16": "无效的天赋等级,天赋等级应小于等于15。", "set_id": "将天赋等级设为 %s。", "set_atk": "将普通攻击等级设为 %s。", - "set_e": "设定元素战技等级为 %s。", - "set_q": "设定元素爆发等级为 %s。", + "set_e": "设置元素战技等级为 %s。", + "set_q": "设置元素爆发等级为 %s。", "invalid_skill_id": "无效的技能ID。", "set_this": "将天赋等级设为 %s。", "invalid_level": "无效的天赋等级。", @@ -299,33 +300,36 @@ "description": "设置当前角色的天赋等级。" }, "teleportAll": { - "success": "已将全部玩家传送到你的位置", - "error": "命令仅限处于多人游戏状态下使用。", + "success": "已将所有玩家传送到你的位置", + "error": "你只能在多人游戏状态下执行此命令。", "description": "将你世界中的所有玩家传送到你所在的位置。" }, "teleport": { - "usage_server": "用法:/tp @ [scene id]", - "usage": "用法:/tp [@] [scene id]", + "usage_server": "用法:/tp @<玩家ID> [场景ID]", + "usage": "用法:/tp [@<玩家ID>] [场景ID]", "specify_player_id": "你必须指定一个玩家ID。", "invalid_position": "无效的位置。", "success": "传送 %s 到坐标 %s,%s,%s,场景为 %s", "description": "改变指定玩家的位置。" }, + "tower": { + "unlock_done": "深境回廊的所有层已全部解锁。" + }, "weather": { - "usage": "用法:weather [climateId]", - "success": "已将当前天气设定为 %s,气候为 %s。", + "usage": "用法:weather <天气ID> [气候ID]", + "success": "已更改天气为 %s,气候为 %s。", "invalid_id": "无效的天气ID。", - "description": "改变天气" + "description": "更改天气" }, "drop": { - "command_usage": "用法:drop [amount]", - "success": "已将 %s x %s 丟在附近。", + "command_usage": "用法:drop <物品ID|物品名称> [数量]", + "success": "已丢下 %s 个 %s。", "description": "在你附近丢一个物品。" }, "help": { "usage": "用法:", "aliases": "別名:", - "available_commands": "可用指令:", + "available_commands": "可用命令:", "description": "发送帮助信息或显示指定命令的信息。" }, "restart": { From ad1d45fa90355e72226988dc6cf9f8ceda159e4b Mon Sep 17 00:00:00 2001 From: tester233 <105267106+tester233@users.noreply.github.com> Date: Tue, 10 May 2022 17:49:50 +0800 Subject: [PATCH 063/312] Update zh-CN.json --- src/main/resources/languages/zh-CN.json | 30 ++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 5a773538d..8736c719a 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -12,9 +12,9 @@ "request": "[Dispatch] 客户端 %s 请求:%s %s", "keystore": { "general_error": "[Dispatch] 加载 keystore 文件时发生错误!", - "password_error": "[Dispatch] 加载 keystore 失败。正在尝试使用默认密码...", - "no_keystore_error": "[Dispatch] 未找到 SSL 证书!已降级到 HTTP 服务器", - "default_password": "[Dispatch] 默认的 keystore 密码加载成功。请考虑将 config.json 的默认密码设置为 123456" + "password_error": "[Dispatch] 加载 keystore 失败。正在尝试使用 keystore 默认密码...", + "no_keystore_error": "[Dispatch] 未找到 SSL 证书!已降级到 HTTP 模式", + "default_password": "[Dispatch] keystore 默认密码加载成功。请考虑将 config.json 的默认密码设置为 123456" }, "no_commands_error": "此命令不适用于 Dispatch-only 模式", "unhandled_request_error": "[Dispatch] 潜在的未处理请求:%s %s", @@ -23,10 +23,10 @@ "login_success": "[Dispatch] 客户端 %s 已登录,UID为 %s", "login_token_attempt": "[Dispatch] 客户端 %s 正在尝试使用 token 登录", "login_token_error": "[Dispatch] 客户端 %s 使用 token 登录失败", - "login_token_success": "[Dispatch] 客户端 %s 已通过 token 登录,UID为 %s", + "login_token_success": "[Dispatch] 客户端 %s 已通过 token 登录,UID 为 %s", "combo_token_success": "[Dispatch] 客户端 %s 交换 token 成功", "combo_token_error": "[Dispatch] 客户端 %s 交换 token 失败", - "account_login_create_success": "[Dispatch] 客户端 %s 登录失败: 已注册UID为 %s 的账号", + "account_login_create_success": "[Dispatch] 客户端 %s 登录失败: 已注册 UID 为 %s 的账号", "account_login_create_error": "[Dispatch] 客户端 %s 登录失败:账号创建失败。", "account_login_exist_error": "[Dispatch] 客户端 %s 登录失败:账号不存在", "account_cache_error": "游戏账号缓存信息错误", @@ -58,7 +58,7 @@ "command_exist_error": "这条命令……好像找不到呢?。", "no_description_specified": "没有指定说明", "invalid": { - "amount": "无效的数量.", + "amount": "无效的数量。", "artifactId": "无效的圣遗物ID。", "avatarId": "无效的角色ID。", "avatarLevel": "无效的角色等级。", @@ -74,7 +74,7 @@ "uid_error": "无效的UID。", "player_exist_error": "玩家不存在。", "player_offline_error": "玩家已离线。", - "item_id_error": "无效的物品ID。.", + "item_id_error": "无效的物品ID。", "item_player_exist_error": "无效的物品/玩家UID。", "entity_id_error": "无效的实体ID。", "player_exist_offline_error": "玩家不存在或已离线。", @@ -93,8 +93,8 @@ "modify": "修改用户账号", "invalid": "无效的UID。", "exists": "账号已存在。", - "create": "已创建账号,UID 为 %s 。", - "delete": "账号已刪除。", + "create": "已创建账号,UID 为 %s。", + "delete": "账号已删除。", "no_account": "账号不存在。", "command_usage": "用法:account <用户名> [uid]", "description": "创建或删除账号。" @@ -151,7 +151,7 @@ "usage": "用法:givechar <玩家> <角色ID|角色名> [数量]", "given": "已将角色 %s (等级 %s) 给与 %s。", "invalid_avatar_id": "无效的角色ID。", - "invalid_avatar_level": "无效的角色等级。.", + "invalid_avatar_level": "无效的角色等级。", "invalid_avatar_or_player_id": "无效的角色ID/玩家ID。", "description": "给予指定角色。" }, @@ -170,7 +170,7 @@ }, "heal": { "success": "已经治疗所有角色。", - "description": "治疗场上队伍的角色。" + "description": "治疗当前队伍的角色。" }, "kick": { "player_kick_player": "玩家 [%s:%s] 已将 [%s:%s] 踢出", @@ -289,8 +289,8 @@ "lower_16": "无效的天赋等级,天赋等级应小于等于15。", "set_id": "将天赋等级设为 %s。", "set_atk": "将普通攻击等级设为 %s。", - "set_e": "设置元素战技等级为 %s。", - "set_q": "设置元素爆发等级为 %s。", + "set_e": "将元素战技等级设为 %s。", + "set_q": "将元素爆发等级设为 %s。", "invalid_skill_id": "无效的技能ID。", "set_this": "将天赋等级设为 %s。", "invalid_level": "无效的天赋等级。", @@ -324,11 +324,11 @@ "drop": { "command_usage": "用法:drop <物品ID|物品名称> [数量]", "success": "已丢下 %s 个 %s。", - "description": "在你附近丢一个物品。" + "description": "在你附近丢下一个物品。" }, "help": { "usage": "用法:", - "aliases": "別名:", + "aliases": "别名:", "available_commands": "可用命令:", "description": "发送帮助信息或显示指定命令的信息。" }, From 70cfe3e42afd416ad5f88a2c0d998f0a2aa9fccc Mon Sep 17 00:00:00 2001 From: tester233 <105267106+tester233@users.noreply.github.com> Date: Tue, 10 May 2022 17:58:32 +0800 Subject: [PATCH 064/312] Update zh-CN.json --- src/main/resources/languages/zh-CN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 8736c719a..f6a2de0ea 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -112,7 +112,7 @@ "description": "切换指定场景。" }, "clear": { - "command_usage": "用法: clear \nall: 所有, wp: 武器, art: 圣遗物, mat: 材料", + "command_usage": "用法:clear \nall: 所有, wp: 武器, art: 圣遗物, mat: 材料", "weapons": "已清除 %s 的武器。", "artifacts": "已清除 %s 的圣遗物。", "materials": "已清除 %s 的材料。", From b1bd0a5a9506897ebe588c8f5f504fb94e4b40db Mon Sep 17 00:00:00 2001 From: Kinesis Date: Tue, 10 May 2022 19:33:04 +0800 Subject: [PATCH 065/312] fixed KillCharacter help command description typo case --- .../emu/grasscutter/command/commands/KillCharacterCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java b/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java index 7435c6d5f..99a57e122 100644 --- a/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java @@ -13,7 +13,7 @@ import java.util.List; import static emu.grasscutter.utils.Language.translate; -@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"}, permission = "player.killcharacter", permissionTargeted = "player.killcharacter.others", description = "commands.killcharacter.description") +@Command(label = "killcharacter", usage = "killcharacter", aliases = {"suicide", "kill"}, permission = "player.killcharacter", permissionTargeted = "player.killcharacter.others", description = "commands.killCharacter.description") public final class KillCharacterCommand implements CommandHandler { @Override From 99dbac8ad368fbf1b4e1895848d5b7d61ac6aac0 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Tue, 10 May 2022 05:02:19 -0700 Subject: [PATCH 066/312] Add UTF8 charset in dispatch server HTML template. --- .../java/emu/grasscutter/server/dispatch/DispatchServer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index 7e4b1655e..f7f14019f 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -252,14 +252,14 @@ public final class DispatchServer { else config.enableCorsForAllOrigins(); } }); - httpServer.get("/", (req, res) -> res.send(translate("messages.status.welcome"))); + httpServer.get("/", (req, res) -> res.send("" + translate("messages.status.welcome") + "")); httpServer.raw().error(404, ctx -> { if(Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING) { Grasscutter.getLogger().info(translate("messages.dispatch.unhandled_request_error", ctx.method(), ctx.url())); } ctx.contentType("text/html"); - ctx.result(""); // I'm like 70% sure this won't break anything. + ctx.result(""); // I'm like 70% sure this won't break anything. }); // Authentication Handler From 2a3708ee461a3bc1432d08857a77b2a451c7e172 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Tue, 10 May 2022 04:15:47 -0700 Subject: [PATCH 067/312] Talent moving stamina cost --- .../game/ability/AbilityManager.java | 17 +- .../managers/StaminaManager/Consumption.java | 17 +- .../StaminaManager/ConsumptionType.java | 26 +- .../StaminaManager/StaminaManager.java | 384 +++++++++++------- .../game/props/PlayerProperty.java | 2 +- .../recv/HandlerEvtDoSkillSuccNotify.java | 11 +- 6 files changed, 286 insertions(+), 171 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java index d1ae388ea..b897f3df5 100644 --- a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java +++ b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java @@ -2,27 +2,22 @@ package emu.grasscutter.game.ability; import com.google.protobuf.InvalidProtocolBufferException; -import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; -import emu.grasscutter.data.custom.AbilityModifier; import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction; -import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierActionType; import emu.grasscutter.data.def.ItemData; import emu.grasscutter.data.custom.AbilityModifierEntry; import emu.grasscutter.game.entity.EntityItem; import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.player.Player; import emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.AbilityActionGenerateElemBall; -import emu.grasscutter.net.proto.AbilityInvokeArgumentOuterClass.AbilityInvokeArgument; import emu.grasscutter.net.proto.AbilityInvokeEntryHeadOuterClass.AbilityInvokeEntryHead; import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; import emu.grasscutter.net.proto.AbilityMetaModifierChangeOuterClass.AbilityMetaModifierChange; import emu.grasscutter.net.proto.AbilityMetaReInitOverrideMapOuterClass.AbilityMetaReInitOverrideMap; -import emu.grasscutter.net.proto.AbilityScalarTypeOuterClass.AbilityScalarType; +import emu.grasscutter.net.proto.AbilityMixinCostStaminaOuterClass.AbilityMixinCostStamina; import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalarValueEntry; import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction; import emu.grasscutter.utils.Position; -import emu.grasscutter.utils.Utils; public class AbilityManager { private Player player; @@ -138,13 +133,9 @@ public class AbilityManager { } } - private void handleMixinCostStamina(AbilityInvokeEntry invoke) { - // Not the right way of doing this - if (Grasscutter.getConfig().OpenStamina) { - // getPlayer().getStaminaManager().updateStamina(getPlayer().getSession(), -450); - // TODO - // set flag in stamina/movement manager that specifies the player is currently using an alternate sprint - } + private void handleMixinCostStamina(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException { + AbilityMixinCostStamina costStamina = AbilityMixinCostStamina.parseFrom((invoke.getAbilityData())); + getPlayer().getStaminaManager().handleMixinCostStamina(costStamina.getIsSwim()); } private void handleGenerateElemBall(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException { diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java index 23eb44be9..a6185f063 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java @@ -1,15 +1,18 @@ package emu.grasscutter.game.managers.StaminaManager; public class Consumption { - public ConsumptionType consumptionType; - public int amount; + public ConsumptionType type = ConsumptionType.None; + public int amount = 0; - public Consumption(ConsumptionType ct, int a) { - consumptionType = ct; - amount = a; + public Consumption(ConsumptionType type, int amount) { + this.type = type; + this.amount = amount; } - public Consumption(ConsumptionType ct) { - this(ct, ct.amount); + public Consumption(ConsumptionType type) { + this(type, type.amount); + } + + public Consumption() { } } diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java index 9afb2171c..feb42d14e 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java @@ -4,23 +4,29 @@ public enum ConsumptionType { None(0), // consume - CLIMB_START(-500), CLIMBING(-150), + CLIMB_START(-500), CLIMB_JUMP(-2500), - SPRINT(-1800), DASH(-360), - FLY(-60), - SWIM_DASH_START(-20), - SWIM_DASH(-204), - SWIMMING(-80), // TODO: Slow swimming is handled per movement, not per second. Movement frequency depends on gender/age/height. 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(-300), // TODO: Get real value + SPRINT(-1800), + SWIM_DASH_START(-20), + 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 - STANDBY(500), + POWERED_FLY(500), // TODO: Get real value + POWERED_SKIFF(2000), // TODO: Get real value RUN(500), - WALK(500), - STANDBY_MOVE(500), - POWERED_FLY(500); + STANDBY(500), + WALK(500); public final int amount; diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index 937ba3b3f..cde40ee57 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -8,7 +8,6 @@ import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.LifeState; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo; -import emu.grasscutter.net.proto.EvtDoSkillSuccNotifyOuterClass.EvtDoSkillSuccNotify; import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; @@ -16,13 +15,94 @@ import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.Position; +import org.jetbrains.annotations.NotNull; import java.lang.Math; import java.util.*; public class StaminaManager { + + // TODO: Skiff state detection? private final Player player; - private HashMap> MotionStatesCategorized = new HashMap<>(); + private final HashMap> MotionStatesCategorized = new HashMap<>() {{ + put("CLIMB", new HashSet<>(List.of( + MotionState.MOTION_CLIMB, // sustained, when not moving no cost no recover + MotionState.MOTION_STANDBY_TO_CLIMB // NOT OBSERVED, see MOTION_JUMP_UP_WALL_FOR_STANDBY + ))); + put("DASH", new HashSet<>(List.of( + MotionState.MOTION_DANGER_DASH, // sustained + MotionState.MOTION_DASH // sustained + ))); + put("FLY", new HashSet<>(List.of( + MotionState.MOTION_FLY, // sustained + MotionState.MOTION_FLY_FAST, // sustained + MotionState.MOTION_FLY_SLOW, // sustained + MotionState.MOTION_POWERED_FLY // sustained, recover + ))); + put("RUN", new HashSet<>(List.of( + MotionState.MOTION_DANGER_RUN, // sustained, recover + MotionState.MOTION_RUN // sustained, recover + ))); + put("SKIFF", new HashSet<>(List.of( + MotionState.MOTION_SKIFF_BOARDING, // NOT OBSERVED even when boarding + MotionState.MOTION_SKIFF_DASH, // NOT OBSERVED even when dashing + MotionState.MOTION_SKIFF_NORMAL, // sustained, OBSERVED when both normal and dashing + MotionState.MOTION_SKIFF_POWERED_DASH // sustained, recover + ))); + put("STANDBY", new HashSet<>(List.of( + MotionState.MOTION_DANGER_STANDBY_MOVE, // sustained, recover + MotionState.MOTION_DANGER_STANDBY, // sustained, recover + MotionState.MOTION_LADDER_TO_STANDBY, // NOT OBSERVED + MotionState.MOTION_STANDBY_MOVE, // sustained, recover + MotionState.MOTION_STANDBY // sustained, recover + ))); + put("SWIM", new HashSet<>(List.of( + MotionState.MOTION_SWIM_IDLE, // sustained + MotionState.MOTION_SWIM_DASH, // immediate and sustained + MotionState.MOTION_SWIM_JUMP, // NOT OBSERVED + MotionState.MOTION_SWIM_MOVE // sustained + ))); + put("WALK", new HashSet<>(List.of( + MotionState.MOTION_DANGER_WALK, // sustained, recover + MotionState.MOTION_WALK // sustained, recover + ))); + put("OTHER", new HashSet<>(List.of( + MotionState.MOTION_CLIMB_JUMP, // cost only once if repeated without switching state + MotionState.MOTION_DASH_BEFORE_SHAKE, // immediate one time sprint charge. + MotionState.MOTION_FIGHT, // immediate, if sustained then subsequent will be MOTION_NOTIFY + MotionState.MOTION_JUMP_UP_WALL_FOR_STANDBY, // immediate, observed when RUN/WALK->CLIMB + MotionState.MOTION_NOTIFY, // can be either cost or recover - check previous state and check skill casting + MotionState.MOTION_SIT_IDLE, // sustained, recover + MotionState.MOTION_JUMP // recover + ))); + put("NOCOST_NORECOVER", new HashSet<>(List.of( + MotionState.MOTION_LADDER_SLIP, // NOT OBSERVED + MotionState.MOTION_SLIP, // sustained, no cost no recover + MotionState.MOTION_FLY_IDLE // NOT OBSERVED + ))); + put("IGNORE", new HashSet<>(List.of( + // these states have no impact on stamina + MotionState.MOTION_CROUCH_IDLE, + MotionState.MOTION_CROUCH_MOVE, + MotionState.MOTION_CROUCH_ROLL, + MotionState.MOTION_DESTROY_VEHICLE, + MotionState.MOTION_FALL_ON_GROUND, + MotionState.MOTION_FOLLOW_ROUTE, + MotionState.MOTION_FORCE_SET_POS, + MotionState.MOTION_GO_UPSTAIRS, + MotionState.MOTION_JUMP_OFF_WALL, + MotionState.MOTION_LADDER_IDLE, + MotionState.MOTION_LADDER_MOVE, + MotionState.MOTION_LAND_SPEED, + MotionState.MOTION_MOVE_FAIL_ACK, + MotionState.MOTION_NONE, + MotionState.MOTION_NUM, + MotionState.MOTION_QUEST_FORCE_DRAG, + MotionState.MOTION_RESET, + MotionState.MOTION_STANDBY_TO_LADDER, + MotionState.MOTION_WATERFALL + ))); + }}; public final static int GlobalMaximumStamina = 24000; private Position currentCoordinates = new Position(0, 0, 0); @@ -33,70 +113,23 @@ public class StaminaManager { private GameSession cachedSession = null; private GameEntity cachedEntity = null; private int staminaRecoverDelay = 0; + private final HashMap beforeUpdateStaminaListeners = new HashMap<>(); + private final HashMap afterUpdateStaminaListeners = new HashMap<>(); + private int lastSkillId = 0; + private int lastSkillCasterId = 0; + private boolean lastSkillFirstTick = true; - private HashMap beforeUpdateStaminaListeners = new HashMap<>(); - private HashMap afterUpdateStaminaListeners = new HashMap<>(); public StaminaManager(Player player) { this.player = player; - - MotionStatesCategorized.put("SWIM", new HashSet<>(Arrays.asList( - MotionState.MOTION_SWIM_MOVE, - MotionState.MOTION_SWIM_IDLE, - MotionState.MOTION_SWIM_DASH, - MotionState.MOTION_SWIM_JUMP - ))); - - MotionStatesCategorized.put("STANDBY", new HashSet<>(Arrays.asList( - MotionState.MOTION_STANDBY, - MotionState.MOTION_STANDBY_MOVE, - MotionState.MOTION_DANGER_STANDBY, - MotionState.MOTION_DANGER_STANDBY_MOVE, - MotionState.MOTION_LADDER_TO_STANDBY, - MotionState.MOTION_JUMP_UP_WALL_FOR_STANDBY - ))); - - MotionStatesCategorized.put("CLIMB", new HashSet<>(Arrays.asList( - MotionState.MOTION_CLIMB, - MotionState.MOTION_CLIMB_JUMP, - MotionState.MOTION_STANDBY_TO_CLIMB, - MotionState.MOTION_LADDER_IDLE, - MotionState.MOTION_LADDER_MOVE, - MotionState.MOTION_LADDER_SLIP, - MotionState.MOTION_STANDBY_TO_LADDER - ))); - - MotionStatesCategorized.put("FLY", new HashSet<>(Arrays.asList( - MotionState.MOTION_FLY, - MotionState.MOTION_FLY_IDLE, - MotionState.MOTION_FLY_SLOW, - MotionState.MOTION_FLY_FAST, - MotionState.MOTION_POWERED_FLY - ))); - - MotionStatesCategorized.put("RUN", new HashSet<>(Arrays.asList( - MotionState.MOTION_DASH, - MotionState.MOTION_DANGER_DASH, - MotionState.MOTION_DASH_BEFORE_SHAKE, - MotionState.MOTION_RUN, - MotionState.MOTION_DANGER_RUN, - MotionState.MOTION_WALK, - MotionState.MOTION_DANGER_WALK - ))); - - MotionStatesCategorized.put("FIGHT", new HashSet<>(Arrays.asList( - MotionState.MOTION_FIGHT - ))); - - MotionStatesCategorized.put("SKIFF", new HashSet<>(Arrays.asList( - MotionState.MOTION_SKIFF_BOARDING, - MotionState.MOTION_SKIFF_NORMAL, - MotionState.MOTION_SKIFF_DASH, - MotionState.MOTION_SKIFF_POWERED_DASH - ))); } - // Listeners + // Accessors + + public void setSkillCast(int skillId, int skillCasterId) { + lastSkillId = skillId; + lastSkillCasterId = skillCasterId; + } public boolean registerBeforeUpdateStaminaListener(String listenerName, BeforeUpdateStaminaListener listener) { if (beforeUpdateStaminaListeners.containsKey(listenerName)) { @@ -146,17 +179,17 @@ public class StaminaManager { } // notify will update for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { - Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.consumptionType.toString(), consumption); - if ((overriddenConsumption.consumptionType != consumption.consumptionType) && (overriddenConsumption.amount != consumption.amount)) { + Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.type.toString(), consumption); + if ((overriddenConsumption.type != consumption.type) && (overriddenConsumption.amount != consumption.amount)) { Grasscutter.getLogger().debug("[StaminaManager] Stamina update relative(" + - consumption.consumptionType.toString() + ", " + consumption.amount + ") overridden to relative(" + - consumption.consumptionType.toString() + ", " + consumption.amount + ") by: " + listener.getKey()); + consumption.type.toString() + ", " + consumption.amount + ") overridden to relative(" + + consumption.type.toString() + ", " + consumption.amount + ") by: " + listener.getKey()); return currentStamina; } } int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); Grasscutter.getLogger().trace(currentStamina + "/" + playerMaxStamina + "\t" + currentState + "\t" + - (isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.consumptionType + "," + + (isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.type + "," + consumption.amount + ")"); int newStamina = currentStamina + consumption.amount; if (newStamina < 0) { @@ -164,7 +197,7 @@ public class StaminaManager { } else if (newStamina > playerMaxStamina) { newStamina = playerMaxStamina; } - return setStamina(session, consumption.consumptionType.toString(), newStamina); + return setStamina(session, consumption.type.toString(), newStamina); } public int updateStaminaAbsolute(GameSession session, String reason, int newStamina) { @@ -236,11 +269,24 @@ public class StaminaManager { // External trigger handler - public void handleEvtDoSkillSuccNotify(GameSession session, EvtDoSkillSuccNotify notify) { - handleImmediateStamina(session, notify); + public void handleEvtDoSkillSuccNotify(GameSession session, int skillId, int casterId) { + // Ignore if skill not cast by not current active + if (casterId != player.getTeamManager().getCurrentAvatarEntity().getId()) { + return; + } + setSkillCast(skillId, casterId); + handleImmediateStamina(session, skillId); } - public void handleCombatInvocationsNotify(GameSession session, EntityMoveInfo moveInfo, GameEntity entity) { + public void handleMixinCostStamina(boolean isSwim) { + // Talent moving and claymore avatar charged attack duration + // Grasscutter.getLogger().trace("abilityMixinCostStamina: isSwim: " + isSwim); + if (lastSkillCasterId == player.getTeamManager().getCurrentAvatarEntity().getId()) { + handleImmediateStamina(cachedSession, lastSkillId); + } + } + + public void handleCombatInvocationsNotify(@NotNull GameSession session, @NotNull EntityMoveInfo moveInfo, @NotNull GameEntity entity) { // cache info for later use in SustainedStaminaHandler tick cachedSession = session; cachedEntity = entity; @@ -252,20 +298,25 @@ public class StaminaManager { return; } currentState = motionState; + // Grasscutter.getLogger().trace("" + currentState); Vector posVector = motionInfo.getPos(); Position newPos = new Position(posVector.getX(), posVector.getY(), posVector.getZ()); if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) { currentCoordinates = newPos; } startSustainedStaminaHandler(); - handleImmediateStamina(session, motionInfo, motionState, entity); + handleImmediateStamina(session, motionState); } // Internal handler - private void handleImmediateStamina(GameSession session, MotionInfo motionInfo, MotionState motionState, - GameEntity entity) { + private void handleImmediateStamina(GameSession session, @NotNull MotionState motionState) { switch (motionState) { + case MOTION_CLIMB: + if (currentState != MotionState.MOTION_CLIMB) { + updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START)); + } + break; case MOTION_DASH_BEFORE_SHAKE: if (previousState != MotionState.MOTION_DASH_BEFORE_SHAKE) { updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT)); @@ -284,8 +335,10 @@ public class StaminaManager { } } - private void handleImmediateStamina(GameSession session, EvtDoSkillSuccNotify notify) { - Consumption consumption = getFightConsumption(notify.getSkillId()); + private void handleImmediateStamina(GameSession session, int skillId) { + // Non-claymore avatar attacks + // TODO: differentiate charged vs normal attack + Consumption consumption = getFightConsumption(skillId); updateStaminaRelative(session, consumption); } @@ -298,32 +351,45 @@ public class StaminaManager { Grasscutter.getLogger().trace("Player moving: " + moving + ", stamina full: " + (currentStamina >= maxStamina) + ", recalculate stamina"); - Consumption consumption = new Consumption(ConsumptionType.None); + Consumption consumption; if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { - consumption = getClimbSustainedConsumption(); - } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { - consumption = getSwimSustainedConsumptions(); - } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { - consumption = getRunWalkDashSustainedConsumption(); + consumption = getClimbConsumption(); + } else if (MotionStatesCategorized.get("DASH").contains(currentState)) { + consumption = getDashConsumption(); } else if (MotionStatesCategorized.get("FLY").contains(currentState)) { - consumption = getFlySustainedConsumption(); + consumption = getFlyConsumption(); + } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { + consumption = new Consumption(ConsumptionType.RUN); + } else if (MotionStatesCategorized.get("SKIFF").contains(currentState)) { + consumption = getSkiffConsumption(); } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { - consumption = getStandSustainedConsumption(); + consumption = new Consumption(ConsumptionType.STANDBY); + } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { + consumption = getSwimConsumptions(); + } else if (MotionStatesCategorized.get("WALK").contains((currentState))) { + consumption = new Consumption(ConsumptionType.WALK); + } else if (MotionStatesCategorized.get("OTHER").contains((currentState))) { + consumption = getOtherConsumptions(); + } else { + // ignore + return; } - - /* + if (consumption.amount < 0) { + /* Do not apply reduction factor when recovering stamina TODO: Reductions that apply to all motion types: Elemental Resonance Wind: -15% Skills - Diona E: -10% while shield lasts - Barbara E: -12% while lasts - */ + Diona E: -10% while shield lasts - applies to SP+MP + Barbara E: -12% while lasts - applies to SP+MP + */ + } + // Delay 2 seconds before starts recovering stamina if (cachedSession != null) { if (consumption.amount < 0) { staminaRecoverDelay = 0; } - if (consumption.amount > 0 && consumption.consumptionType != ConsumptionType.POWERED_FLY) { + if (consumption.amount > 0 && consumption.type != ConsumptionType.POWERED_FLY) { // For POWERED_FLY recover immediately - things like Amber's gliding exam may require this. if (staminaRecoverDelay < 10) { // For others recover after 2 seconds (10 ticks) - as official server does. @@ -384,91 +450,137 @@ public class StaminaManager { */ // TODO: Currently only handling Ayaka and Mona's talent moving initial costs. - Consumption consumption = new Consumption(ConsumptionType.None); - HashMap fightingCost = new HashMap<>() {{ - put(10013, -1000); // Kamisato Ayaka - put(10413, -1000); // Mona - }}; - if (fightingCost.containsKey(skillCasting)) { - consumption = new Consumption(ConsumptionType.FIGHT, fightingCost.get(skillCasting)); - } - return consumption; - } + Consumption consumption = new Consumption(); - private Consumption getClimbSustainedConsumption() { - Consumption consumption = new Consumption(ConsumptionType.None); - if (currentState == MotionState.MOTION_CLIMB && isPlayerMoving()) { - consumption = new Consumption(ConsumptionType.CLIMBING); - if (previousState != MotionState.MOTION_CLIMB && previousState != MotionState.MOTION_CLIMB_JUMP) { - consumption = new Consumption(ConsumptionType.CLIMB_START); + // Talent moving + HashMap> talentMovementConsumptions = new HashMap<>() {{ + // List[0] = initial cost, [1] = sustained cost. Sustained costs are divided by 3 per second as MixinStaminaCost is triggered at 3Hz. + put(10013, List.of(new Consumption(ConsumptionType.TALENT_DASH_START, -1000), new Consumption(ConsumptionType.TALENT_DASH, -500))); // Kamisato Ayaka + put(10413, List.of(new Consumption(ConsumptionType.TALENT_DASH_START, -1000), new Consumption(ConsumptionType.TALENT_DASH, -500))); // Mona + }}; + if (talentMovementConsumptions.containsKey(skillCasting)) { + if (lastSkillFirstTick) { + consumption = talentMovementConsumptions.get(skillCasting).get(1); + } else { + lastSkillFirstTick = false; + consumption = talentMovementConsumptions.get(skillCasting).get(0); } } - // TODO: Foods + // TODO: Claymore avatar charged attack + // HashMap fightConsumptions = new HashMap<>(); + + // TODO: Non-claymore avatar charged attack + return consumption; } - private Consumption getSwimSustainedConsumptions() { + private Consumption getClimbConsumption() { + Consumption consumption = new Consumption(); + if (currentState == MotionState.MOTION_CLIMB && isPlayerMoving()) { + consumption.type = ConsumptionType.CLIMBING; + consumption.amount = ConsumptionType.CLIMBING.amount; + } + // Climbing specific reductions + // TODO: create a food cost reduction map + HashMap foodReductionMap = new HashMap<>() {{ + // TODO: get real talent id + put(0, 0.8f); // Sample food + }}; + consumption.amount *= getFoodCostReductionFactor(foodReductionMap); + + HashMap talentReductionMap = new HashMap<>() {{ + // TODO: get real talent id + put(0, 0.8f); // Xiao + }}; + consumption.amount *= getTalentCostReductionFactor(talentReductionMap); + return consumption; + } + + private Consumption getSwimConsumptions() { handleDrowning(); - Consumption consumption = new Consumption(ConsumptionType.None); + Consumption consumption = new Consumption(); if (currentState == MotionState.MOTION_SWIM_MOVE) { - consumption = new Consumption(ConsumptionType.SWIMMING); + consumption.type = ConsumptionType.SWIMMING; + consumption.amount = ConsumptionType.SWIMMING.amount; } if (currentState == MotionState.MOTION_SWIM_DASH) { - consumption = new Consumption(ConsumptionType.SWIM_DASH); + consumption.type = ConsumptionType.SWIM_DASH; + consumption.amount = ConsumptionType.SWIM_DASH.amount; } + // Reductions + HashMap talentReductionMap = new HashMap<>() {{ + // TODO: get real talent id + put(0, 0.8f); // Beidou + put(1, 0.8f); // Sangonomiya Kokomi + }}; + consumption.amount *= getTalentCostReductionFactor(talentReductionMap); return consumption; } - private Consumption getRunWalkDashSustainedConsumption() { - Consumption consumption = new Consumption(ConsumptionType.None); + private Consumption getDashConsumption() { + Consumption consumption = new Consumption(); if (currentState == MotionState.MOTION_DASH) { - consumption = new Consumption(ConsumptionType.DASH); - // TODO: Foods - } - if (currentState == MotionState.MOTION_RUN) { - consumption = new Consumption(ConsumptionType.RUN); - } - if (currentState == MotionState.MOTION_WALK) { - consumption = new Consumption(ConsumptionType.WALK); + consumption.type = ConsumptionType.DASH; + consumption.amount = ConsumptionType.DASH.amount; + // TODO: Dashing specific reductions + // Foods: } return consumption; } - private Consumption getFlySustainedConsumption() { + private Consumption getFlyConsumption() { // POWERED_FLY, e.g. wind tunnel if (currentState == MotionState.MOTION_POWERED_FLY) { return new Consumption(ConsumptionType.POWERED_FLY); } Consumption consumption = new Consumption(ConsumptionType.FLY); - // Talent - HashMap glidingCostReduction = new HashMap<>() {{ + // Passive Talents + HashMap talentReductionMap = new HashMap<>() {{ put(212301, 0.8f); // Amber put(222301, 0.8f); // Venti }}; + consumption.amount *= getTalentCostReductionFactor(talentReductionMap); + // TODO: Foods + return consumption; + } + + private Consumption getSkiffConsumption() { + // POWERED_SKIFF, e.g. wind tunnel + if (currentState == MotionState.MOTION_SKIFF_POWERED_DASH) { + return new Consumption(ConsumptionType.POWERED_SKIFF); + } + Consumption consumption = new Consumption(ConsumptionType.SKIFF); + // No known reduction for skiffing. + return consumption; + } + + private Consumption getOtherConsumptions() { + // TODO: Add logic + return new Consumption(); + } + + // Reduction getter + + private float getTalentCostReductionFactor(HashMap talentReductionMap) { + // All known talents reductions are not stackable float reduction = 1; for (EntityAvatar entity : cachedSession.getPlayer().getTeamManager().getActiveTeam()) { for (int skillId : entity.getAvatar().getProudSkillList()) { - if (glidingCostReduction.containsKey(skillId)) { - float potentialLowerReduction = glidingCostReduction.get(skillId); + if (talentReductionMap.containsKey(skillId)) { + float potentialLowerReduction = talentReductionMap.get(skillId); if (potentialLowerReduction < reduction) { reduction = potentialLowerReduction; } } } } - consumption.amount *= reduction; - // TODO: Foods - return consumption; + return reduction; } - private Consumption getStandSustainedConsumption() { - Consumption consumption = new Consumption(ConsumptionType.None); - if (currentState == MotionState.MOTION_STANDBY) { - consumption = new Consumption(ConsumptionType.STANDBY); - } - if (currentState == MotionState.MOTION_STANDBY_MOVE) { - consumption = new Consumption(ConsumptionType.STANDBY_MOVE); - } - return consumption; + private float getFoodCostReductionFactor(HashMap foodReductionMap) { + // All known food reductions are not stackable + // TODO: Check consumed food (buff?) and return proper factor + float reduction = 1; + return reduction; } } diff --git a/src/main/java/emu/grasscutter/game/props/PlayerProperty.java b/src/main/java/emu/grasscutter/game/props/PlayerProperty.java index 3cb67d9bb..85a9456cf 100644 --- a/src/main/java/emu/grasscutter/game/props/PlayerProperty.java +++ b/src/main/java/emu/grasscutter/game/props/PlayerProperty.java @@ -30,7 +30,7 @@ public enum PlayerProperty { // his gems and then got a money refund, so negative is allowed. PROP_PLAYER_SCOIN (10016), // Mora [0, +inf) PROP_PLAYER_MP_SETTING_TYPE (10017), // Do you allow other players to join your game? [0=no 1=direct 2=approval] - PROP_IS_MP_MODE_AVAILABLE (10018), // Are you not in a quest or something that disables MP? [0, 1] + PROP_IS_MP_MODE_AVAILABLE (10018), // 0 if in quest or something that disables MP [0, 1] PROP_PLAYER_WORLD_LEVEL (10019), // [0, 8] PROP_PLAYER_RESIN (10020), // Original Resin [0, +inf) PROP_PLAYER_WAIT_SUB_HCOIN (10022), diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java index 705341fa0..36aa733c2 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java @@ -4,7 +4,9 @@ import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.EvtDoSkillSuccNotifyOuterClass.EvtDoSkillSuccNotify; +import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.utils.Position; @Opcodes(PacketOpcodes.EvtDoSkillSuccNotify) public class HandlerEvtDoSkillSuccNotify extends PacketHandler { @@ -12,9 +14,10 @@ public class HandlerEvtDoSkillSuccNotify extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { EvtDoSkillSuccNotify notify = EvtDoSkillSuccNotify.parseFrom(payload); - // TODO: Will be used for deducting stamina for charged skills. - - session.getPlayer().getStaminaManager().handleEvtDoSkillSuccNotify(session, notify); + int skillId = notify.getSkillId(); + int casterId = notify.getCasterId(); + Vector forwardVector = notify.getForward(); + Position forward = new Position(forwardVector.getX(), forwardVector.getY(), forwardVector.getZ()); + session.getPlayer().getStaminaManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); } - } From e9f7b0dfb3e72bb53b00516caa21069a29903169 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Tue, 10 May 2022 04:23:08 -0700 Subject: [PATCH 068/312] Add AbilityMixinStaminaCost proto From 0f1341512c2e354db1a2712fe07e64db21c00cea Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Tue, 10 May 2022 05:00:57 -0700 Subject: [PATCH 069/312] Reset first tick on new skill --- .../game/managers/StaminaManager/StaminaManager.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index cde40ee57..8737d9755 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -127,6 +127,7 @@ public class StaminaManager { // Accessors public void setSkillCast(int skillId, int skillCasterId) { + lastSkillFirstTick = true; lastSkillId = skillId; lastSkillCasterId = skillCasterId; } @@ -460,10 +461,10 @@ public class StaminaManager { }}; if (talentMovementConsumptions.containsKey(skillCasting)) { if (lastSkillFirstTick) { - consumption = talentMovementConsumptions.get(skillCasting).get(1); + consumption = talentMovementConsumptions.get(skillCasting).get(0); } else { lastSkillFirstTick = false; - consumption = talentMovementConsumptions.get(skillCasting).get(0); + consumption = talentMovementConsumptions.get(skillCasting).get(1); } } // TODO: Claymore avatar charged attack From ecf028d0c6ec84d7f299a9b558e16a4cc91c2875 Mon Sep 17 00:00:00 2001 From: Secretboy <74841174+Secretboy-SMR@users.noreply.github.com> Date: Tue, 10 May 2022 20:33:45 +0800 Subject: [PATCH 070/312] add /language command (#780) * Fix the following issues: 1. HashMap non-thread-safe issus 2. Fix the same problem in pr621, but use a better implementation Add the following functions: 1. There is now a language cache inside getLanguage to prepare for different languages corresponding to different time zones where the accounts in the server are located * add /language command,each account has their own Locate --- .../java/emu/grasscutter/Grasscutter.java | 8 ++ .../command/commands/AccountCommand.java | 16 +-- .../command/commands/BroadcastCommand.java | 4 +- .../command/commands/ChangeSceneCommand.java | 12 +- .../command/commands/ClearCommand.java | 24 ++-- .../command/commands/CoopCommand.java | 12 +- .../command/commands/DropCommand.java | 14 +- .../command/commands/EnterDungeonCommand.java | 12 +- .../command/commands/GiveAllCommand.java | 10 +- .../command/commands/GiveArtifactCommand.java | 14 +- .../command/commands/GiveCharCommand.java | 14 +- .../command/commands/GiveCommand.java | 24 ++-- .../command/commands/GodModeCommand.java | 4 +- .../command/commands/HealCommand.java | 4 +- .../command/commands/HelpCommand.java | 26 ++-- .../command/commands/KickCommand.java | 6 +- .../command/commands/KillAllCommand.java | 10 +- .../commands/KillCharacterCommand.java | 4 +- .../command/commands/ListCommand.java | 2 +- .../command/commands/PermissionCommand.java | 18 +-- .../command/commands/PositionCommand.java | 4 +- .../command/commands/ReloadCommand.java | 4 +- .../command/commands/ResetConstCommand.java | 8 +- .../commands/ResetShopLimitCommand.java | 4 +- .../command/commands/SendMailCommand.java | 52 +++---- .../command/commands/SendMessageCommand.java | 8 +- .../commands/SetFetterLevelCommand.java | 10 +- .../command/commands/SetStatsCommand.java | 12 +- .../commands/SetWorldLevelCommand.java | 10 +- .../command/commands/SpawnCommand.java | 14 +- .../command/commands/StopCommand.java | 4 +- .../command/commands/TalentCommand.java | 34 ++--- .../command/commands/TeleportAllCommand.java | 6 +- .../command/commands/TeleportCommand.java | 12 +- .../command/commands/UnlockTowerCommand.java | 2 +- .../command/commands/WeatherCommand.java | 10 +- .../java/emu/grasscutter/game/Account.java | 16 +++ .../java/emu/grasscutter/utils/Language.java | 130 +++++++++++++++--- src/main/resources/languages/en-US.json | 5 + src/main/resources/languages/zh-CN.json | 5 + 40 files changed, 356 insertions(+), 232 deletions(-) diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index f426d41af..8bdb3c207 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -206,6 +206,14 @@ public final class Grasscutter { return language; } + public static void setLanguage(Language language) { + Grasscutter.language = language; + } + + public static Language getLanguage(String langCode) { + return Language.getLanguage(langCode); + } + public static Logger getLogger() { return log; } diff --git a/src/main/java/emu/grasscutter/command/commands/AccountCommand.java b/src/main/java/emu/grasscutter/command/commands/AccountCommand.java index a16cc6480..2669ff8b9 100644 --- a/src/main/java/emu/grasscutter/command/commands/AccountCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/AccountCommand.java @@ -17,12 +17,12 @@ public final class AccountCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (sender != null) { - CommandHandler.sendMessage(sender, translate("commands.generic.console_execute_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.console_execute_error")); return; } if (args.size() < 2) { - CommandHandler.sendMessage(null, translate("commands.account.command_usage")); + CommandHandler.sendMessage(null, translate(sender, "commands.account.command_usage")); return; } @@ -31,7 +31,7 @@ public final class AccountCommand implements CommandHandler { switch (action) { default: - CommandHandler.sendMessage(null, translate("commands.account.command_usage")); + CommandHandler.sendMessage(null, translate(sender, "commands.account.command_usage")); return; case "create": int uid = 0; @@ -39,20 +39,20 @@ public final class AccountCommand implements CommandHandler { try { uid = Integer.parseInt(args.get(2)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(null, translate("commands.account.invalid")); + CommandHandler.sendMessage(null, translate(sender, "commands.account.invalid")); return; } } emu.grasscutter.game.Account account = DatabaseHelper.createAccountWithId(username, uid); if (account == null) { - CommandHandler.sendMessage(null, translate("commands.account.exists")); + CommandHandler.sendMessage(null, translate(sender, "commands.account.exists")); return; } else { account.addPermission("*"); account.save(); // Save account to database. - CommandHandler.sendMessage(null, translate("commands.account.create", Integer.toString(account.getPlayerUid()))); + CommandHandler.sendMessage(null, translate(sender, "commands.account.create", Integer.toString(account.getPlayerUid()))); } return; case "delete": @@ -60,7 +60,7 @@ public final class AccountCommand implements CommandHandler { Account toDelete = DatabaseHelper.getAccountByName(username); if (toDelete == null) { - CommandHandler.sendMessage(null, translate("commands.account.no_account")); + CommandHandler.sendMessage(null, translate(sender, "commands.account.no_account")); return; } @@ -73,7 +73,7 @@ public final class AccountCommand implements CommandHandler { // Finally, we do the actual deletion. DatabaseHelper.deleteAccount(toDelete); - CommandHandler.sendMessage(null, translate("commands.account.delete")); + CommandHandler.sendMessage(null, translate(sender, "commands.account.delete")); } } } diff --git a/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java b/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java index 95f0c7c05..86080c822 100644 --- a/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/BroadcastCommand.java @@ -15,7 +15,7 @@ public final class BroadcastCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (args.size() < 1) { - CommandHandler.sendMessage(sender, translate("commands.broadcast.command_usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.broadcast.command_usage")); return; } @@ -25,6 +25,6 @@ public final class BroadcastCommand implements CommandHandler { CommandHandler.sendMessage(p, message); } - CommandHandler.sendMessage(sender, translate("commands.broadcast.message_sent")); + CommandHandler.sendMessage(sender, translate(sender, "commands.broadcast.message_sent")); } } diff --git a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java index 59706dd96..c653acee1 100644 --- a/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ChangeSceneCommand.java @@ -14,31 +14,31 @@ public final class ChangeSceneCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } if (args.size() != 1) { - CommandHandler.sendMessage(sender, translate("commands.changescene.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.changescene.usage")); return; } try { int sceneId = Integer.parseInt(args.get(0)); if (sceneId == targetPlayer.getSceneId()) { - CommandHandler.sendMessage(sender, translate("commands.changescene.already_in_scene")); + CommandHandler.sendMessage(sender, translate(sender, "commands.changescene.already_in_scene")); return; } boolean result = targetPlayer.getWorld().transferPlayerToScene(targetPlayer, sceneId, targetPlayer.getPos()); if (!result) { - CommandHandler.sendMessage(sender, translate("commands.changescene.exists_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.changescene.exists_error")); return; } - CommandHandler.sendMessage(sender, translate("commands.changescene.success", Integer.toString(sceneId))); + CommandHandler.sendMessage(sender, translate(sender, "commands.changescene.success", Integer.toString(sceneId))); } catch (Exception e) { - CommandHandler.sendMessage(sender, translate("commands.execution.argument_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error")); } } } diff --git a/src/main/java/emu/grasscutter/command/commands/ClearCommand.java b/src/main/java/emu/grasscutter/command/commands/ClearCommand.java index ab0ff0eb1..7b50ee034 100644 --- a/src/main/java/emu/grasscutter/command/commands/ClearCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ClearCommand.java @@ -21,11 +21,11 @@ public final class ClearCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } if (args.size() < 1) { - CommandHandler.sendMessage(sender, translate("commands.clear.command_usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.clear.command_usage")); return; } Inventory playerInventory = targetPlayer.getInventory(); @@ -37,7 +37,7 @@ public final class ClearCommand implements CommandHandler { .filter(item -> item.getItemType() == ItemType.ITEM_WEAPON) .filter(item -> !item.isLocked() && !item.isEquipped()) .toList(); - CommandHandler.sendMessage(sender, translate("commands.clear.weapons", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate(sender, "commands.clear.weapons", targetPlayer.getNickname())); } case "art" -> { toDelete = playerInventory.getItems().values().stream() @@ -45,7 +45,7 @@ public final class ClearCommand implements CommandHandler { .filter(item -> item.getLevel() == 1 && item.getExp() == 0) .filter(item -> !item.isLocked() && !item.isEquipped()) .toList(); - CommandHandler.sendMessage(sender, translate("commands.clear.artifacts", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate(sender, "commands.clear.artifacts", targetPlayer.getNickname())); } case "mat" -> { toDelete = playerInventory.getItems().values().stream() @@ -53,7 +53,7 @@ public final class ClearCommand implements CommandHandler { .filter(item -> item.getLevel() == 1 && item.getExp() == 0) .filter(item -> !item.isLocked() && !item.isEquipped()) .toList(); - CommandHandler.sendMessage(sender, translate("commands.clear.materials", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate(sender, "commands.clear.materials", targetPlayer.getNickname())); } case "all" -> { toDelete = playerInventory.getItems().values().stream() @@ -61,7 +61,7 @@ public final class ClearCommand implements CommandHandler { .filter(item1 -> item1.getLevel() == 1 && item1.getExp() == 0) .filter(item1 -> !item1.isLocked() && !item1.isEquipped()) .toList(); - CommandHandler.sendMessage(sender, translate("commands.clear.artifacts", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate(sender, "commands.clear.artifacts", targetPlayer.getNickname())); playerInventory.removeItems(toDelete); toDelete = playerInventory.getItems().values().stream() @@ -69,7 +69,7 @@ public final class ClearCommand implements CommandHandler { .filter(item2 -> !item2.isLocked() && !item2.isEquipped()) .toList(); playerInventory.removeItems(toDelete); - CommandHandler.sendMessage(sender, translate("commands.clear.materials", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate(sender, "commands.clear.materials", targetPlayer.getNickname())); toDelete = playerInventory.getItems().values().stream() .filter(item3 -> item3.getItemType() == ItemType.ITEM_WEAPON) @@ -77,28 +77,28 @@ public final class ClearCommand implements CommandHandler { .filter(item3 -> !item3.isLocked() && !item3.isEquipped()) .toList(); playerInventory.removeItems(toDelete); - CommandHandler.sendMessage(sender, translate("commands.clear.weapons", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate(sender, "commands.clear.weapons", targetPlayer.getNickname())); toDelete = playerInventory.getItems().values().stream() .filter(item4 -> item4.getItemType() == ItemType.ITEM_FURNITURE) .filter(item4 -> !item4.isLocked() && !item4.isEquipped()) .toList(); playerInventory.removeItems(toDelete); - CommandHandler.sendMessage(sender, translate("commands.clear.furniture", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate(sender, "commands.clear.furniture", targetPlayer.getNickname())); toDelete = playerInventory.getItems().values().stream() .filter(item5 -> item5.getItemType() == ItemType.ITEM_DISPLAY) .filter(item5 -> !item5.isLocked() && !item5.isEquipped()) .toList(); playerInventory.removeItems(toDelete); - CommandHandler.sendMessage(sender, translate("commands.clear.displays", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate(sender, "commands.clear.displays", targetPlayer.getNickname())); toDelete = playerInventory.getItems().values().stream() .filter(item6 -> item6.getItemType() == ItemType.ITEM_VIRTUAL) .filter(item6 -> !item6.isLocked() && !item6.isEquipped()) .toList(); - CommandHandler.sendMessage(sender, translate("commands.clear.virtuals", targetPlayer.getNickname())); - CommandHandler.sendMessage(sender, translate("commands.clear.everything", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate(sender, "commands.clear.virtuals", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate(sender, "commands.clear.everything", targetPlayer.getNickname())); } } diff --git a/src/main/java/emu/grasscutter/command/commands/CoopCommand.java b/src/main/java/emu/grasscutter/command/commands/CoopCommand.java index a86472978..509c554b7 100644 --- a/src/main/java/emu/grasscutter/command/commands/CoopCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/CoopCommand.java @@ -15,14 +15,14 @@ public final class CoopCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } Player host = sender; switch (args.size()) { case 0: // Summon target to self - CommandHandler.sendMessage(sender, translate("commands.coop.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.coop.usage")); if (sender == null) // Console doesn't have a self to summon to return; break; @@ -31,16 +31,16 @@ public final class CoopCommand implements CommandHandler { int hostId = Integer.parseInt(args.get(0)); host = Grasscutter.getGameServer().getPlayerByUid(hostId); if (host == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.player_offline_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.player_offline_error")); return; } break; } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.execution.uid_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.uid_error")); return; } default: - CommandHandler.sendMessage(sender, translate("commands.coop.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.coop.usage")); return; } @@ -50,6 +50,6 @@ public final class CoopCommand implements CommandHandler { } host.getServer().getMultiplayerManager().applyEnterMp(targetPlayer, host.getUid()); targetPlayer.getServer().getMultiplayerManager().applyEnterMpReply(host, targetPlayer.getUid(), true); - CommandHandler.sendMessage(sender, translate("commands.coop.success", targetPlayer.getNickname(), host.getNickname())); + CommandHandler.sendMessage(sender, translate(sender, "commands.coop.success", targetPlayer.getNickname(), host.getNickname())); } } diff --git a/src/main/java/emu/grasscutter/command/commands/DropCommand.java b/src/main/java/emu/grasscutter/command/commands/DropCommand.java index e2579a7be..eecec06e9 100644 --- a/src/main/java/emu/grasscutter/command/commands/DropCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/DropCommand.java @@ -19,7 +19,7 @@ public final class DropCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(null, translate("commands.execution.need_target")); + CommandHandler.sendMessage(null, translate(sender, "commands.execution.need_target")); return; } @@ -31,25 +31,25 @@ public final class DropCommand implements CommandHandler { try { amount = Integer.parseInt(args.get(1)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.generic.invalid.amount")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.amount")); return; } // Slightly cheeky here: no break, so it falls through to initialize the first argument too case 1: try { item = Integer.parseInt(args.get(0)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemId")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId")); return; } break; default: - CommandHandler.sendMessage(sender, translate("commands.drop.command_usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.drop.command_usage")); return; } ItemData itemData = GameData.getItemDataMap().get(item); if (itemData == null) { - CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemId")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId")); return; } if (itemData.isEquip()) { @@ -63,6 +63,6 @@ public final class DropCommand implements CommandHandler { EntityItem entity = new EntityItem(targetPlayer.getScene(), targetPlayer, itemData, targetPlayer.getPos().clone().addY(3f), amount); targetPlayer.getScene().addEntity(entity); } - CommandHandler.sendMessage(sender, translate("commands.drop.success", Integer.toString(amount), Integer.toString(item))); + CommandHandler.sendMessage(sender, translate(sender, "commands.drop.success", Integer.toString(amount), Integer.toString(item))); } -} \ No newline at end of file +} diff --git a/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java b/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java index c4e37a93e..b44952393 100644 --- a/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/EnterDungeonCommand.java @@ -14,30 +14,30 @@ public final class EnterDungeonCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(null, translate("commands.execution.need_target")); + CommandHandler.sendMessage(null, translate(sender, "commands.execution.need_target")); return; } if (args.size() < 1) { - CommandHandler.sendMessage(sender, translate("commands.enter_dungeon.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.enter_dungeon.usage")); return; } try { int dungeonId = Integer.parseInt(args.get(0)); if (dungeonId == targetPlayer.getSceneId()) { - CommandHandler.sendMessage(sender, translate("commands.enter_dungeon.in_dungeon_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.enter_dungeon.in_dungeon_error")); return; } boolean result = targetPlayer.getServer().getDungeonManager().enterDungeon(targetPlayer.getSession().getPlayer(), 0, dungeonId); - CommandHandler.sendMessage(sender, translate("commands.enter_dungeon.changed", dungeonId)); + CommandHandler.sendMessage(sender, translate(sender, "commands.enter_dungeon.changed", dungeonId)); if (!result) { - CommandHandler.sendMessage(sender, translate("commands.enter_dungeon.not_found_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.enter_dungeon.not_found_error")); } } catch (Exception e) { - CommandHandler.sendMessage(sender, translate("commands.enter_dungeon.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.enter_dungeon.usage")); } } } diff --git a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java index 6b9104626..3a0dee111 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveAllCommand.java @@ -21,7 +21,7 @@ public final class GiveAllCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } int amount = 99999; @@ -33,21 +33,21 @@ public final class GiveAllCommand implements CommandHandler { try { amount = Integer.parseInt(args.get(0)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.generic.invalid.amount")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.amount")); return; } break; default: // invalid - CommandHandler.sendMessage(sender, translate("commands.giveAll.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.giveAll.usage")); return; } this.giveAllItems(targetPlayer, amount); - CommandHandler.sendMessage(sender, translate("commands.giveAll.success", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate(targetPlayer, "commands.giveAll.success", targetPlayer.getNickname())); } public void giveAllItems(Player player, int amount) { - CommandHandler.sendMessage(player, translate("commands.giveAll.started")); + CommandHandler.sendMessage(player, translate(player, "commands.giveAll.started")); for (AvatarData avatarData: GameData.getAvatarDataMap().values()) { //Exclude test avatar diff --git a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java index 25503dbd3..6f3a02a5e 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveArtifactCommand.java @@ -115,11 +115,11 @@ public final class GiveArtifactCommand implements CommandHandler { public void execute(Player sender, Player targetPlayer, List args) { // Sanity checks if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } if (args.size() < 2) { - CommandHandler.sendMessage(sender, translate("commands.giveArtifact.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.giveArtifact.usage")); return; } @@ -128,13 +128,13 @@ public final class GiveArtifactCommand implements CommandHandler { try { itemId = Integer.parseInt(args.remove(0)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.giveArtifact.id_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.giveArtifact.id_error")); return; } ItemData itemData = GameData.getItemDataMap().get(itemId); if (itemData.getItemType() != ItemType.ITEM_RELIQUARY) { - CommandHandler.sendMessage(sender, translate("commands.giveArtifact.id_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.giveArtifact.id_error")); return; } @@ -155,7 +155,7 @@ public final class GiveArtifactCommand implements CommandHandler { } if (mainPropId == -1) { - CommandHandler.sendMessage(sender, translate("commands.execution.argument_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error")); return; } @@ -194,7 +194,7 @@ public final class GiveArtifactCommand implements CommandHandler { appendPropIdList.addAll(Collections.nCopies(n, appendPropId)); }); } catch (Exception ignored) { - CommandHandler.sendMessage(sender, translate("commands.execution.argument_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error")); return; } @@ -206,7 +206,7 @@ public final class GiveArtifactCommand implements CommandHandler { item.getAppendPropIdList().addAll(appendPropIdList); targetPlayer.getInventory().addItem(item, ActionReason.SubfieldDrop); - CommandHandler.sendMessage(sender, translate("commands.giveArtifact.success", Integer.toString(itemId), Integer.toString(targetPlayer.getUid()))); + CommandHandler.sendMessage(sender, translate(sender, "commands.giveArtifact.success", Integer.toString(itemId), Integer.toString(targetPlayer.getUid()))); } } diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java index af789f394..2917c64c4 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java @@ -18,7 +18,7 @@ public final class GiveCharCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } @@ -31,7 +31,7 @@ public final class GiveCharCommand implements CommandHandler { level = Integer.parseInt(args.get(1)); } catch (NumberFormatException ignored) { // TODO: Parse from avatar name using GM Handbook. - CommandHandler.sendMessage(sender, translate("commands.execution.invalid.avatarLevel")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.invalid.avatarLevel")); return; } // Cheeky fall-through to parse first argument too case 1: @@ -39,24 +39,24 @@ public final class GiveCharCommand implements CommandHandler { avatarId = Integer.parseInt(args.get(0)); } catch (NumberFormatException ignored) { // TODO: Parse from avatar name using GM Handbook. - CommandHandler.sendMessage(sender, translate("commands.execution.invalid.avatarId")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.invalid.avatarId")); return; } break; default: - CommandHandler.sendMessage(sender, translate("commands.giveChar.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.giveChar.usage")); return; } AvatarData avatarData = GameData.getAvatarDataMap().get(avatarId); if (avatarData == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.invalid.avatarId")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.invalid.avatarId")); return; } // Check level. if (level > 90) { - CommandHandler.sendMessage(sender, translate("commands.execution.invalid.avatarLevel")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.invalid.avatarLevel")); return; } @@ -76,6 +76,6 @@ public final class GiveCharCommand implements CommandHandler { avatar.recalcStats(); targetPlayer.addAvatar(avatar); - CommandHandler.sendMessage(sender, translate("commands.giveChar.given", Integer.toString(avatarId), Integer.toString(level), Integer.toString(targetPlayer.getUid()))); + CommandHandler.sendMessage(sender, translate(sender, "commands.giveChar.given", Integer.toString(avatarId), Integer.toString(level), Integer.toString(targetPlayer.getUid()))); } } diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java index a60991bad..9ce353419 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCommand.java @@ -34,7 +34,7 @@ public final class GiveCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } int item; @@ -68,21 +68,21 @@ public final class GiveCommand implements CommandHandler { try { refinement = Integer.parseInt(args.get(3)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemRefinement")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemRefinement")); return; } // Fallthrough case 3: // [amount] [level] try { lvl = Integer.parseInt(args.get(2)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemLevel")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemLevel")); return; } // Fallthrough case 2: // [amount] try { amount = Integer.parseInt(args.get(1)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.generic.invalid.amount")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.amount")); return; } // Fallthrough case 1: // @@ -90,28 +90,28 @@ public final class GiveCommand implements CommandHandler { item = Integer.parseInt(args.get(0)); } catch (NumberFormatException ignored) { // TODO: Parse from item name using GM Handbook. - CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemId")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId")); return; } break; default: // *No args* - CommandHandler.sendMessage(sender, translate("commands.give.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.give.usage")); return; } ItemData itemData = GameData.getItemDataMap().get(item); if (itemData == null) { - CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemId")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId")); return; } if (refinement != 0) { if (itemData.getItemType() == ItemType.ITEM_WEAPON) { if (refinement < 1 || refinement > 5) { - CommandHandler.sendMessage(sender, translate("commands.give.refinement_must_between_1_and_5")); + CommandHandler.sendMessage(sender, translate(sender, "commands.give.refinement_must_between_1_and_5")); return; } } else { - CommandHandler.sendMessage(sender, translate("commands.give.refinement_only_applicable_weapons")); + CommandHandler.sendMessage(sender, translate(sender, "commands.give.refinement_only_applicable_weapons")); return; } } @@ -119,11 +119,11 @@ public final class GiveCommand implements CommandHandler { this.item(targetPlayer, itemData, amount, lvl, refinement); if (!itemData.isEquip()) { - CommandHandler.sendMessage(sender, translate("commands.give.given", Integer.toString(amount), Integer.toString(item), Integer.toString(targetPlayer.getUid()))); + CommandHandler.sendMessage(sender, translate(sender, "commands.give.given", Integer.toString(amount), Integer.toString(item), Integer.toString(targetPlayer.getUid()))); } else if (itemData.getItemType() == ItemType.ITEM_WEAPON) { - CommandHandler.sendMessage(sender, translate("commands.give.given_with_level_and_refinement", Integer.toString(item), Integer.toString(lvl), Integer.toString(refinement), Integer.toString(amount), Integer.toString(targetPlayer.getUid()))); + CommandHandler.sendMessage(sender, translate(sender, "commands.give.given_with_level_and_refinement", Integer.toString(item), Integer.toString(lvl), Integer.toString(refinement), Integer.toString(amount), Integer.toString(targetPlayer.getUid()))); } else { - CommandHandler.sendMessage(sender, translate("commands.give.given_level", Integer.toString(item), Integer.toString(lvl), Integer.toString(amount), Integer.toString(targetPlayer.getUid()))); + CommandHandler.sendMessage(sender, translate(sender, "commands.give.given_level", Integer.toString(item), Integer.toString(lvl), Integer.toString(amount), Integer.toString(targetPlayer.getUid()))); } } diff --git a/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java b/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java index 98a375838..9b9160bcd 100644 --- a/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GodModeCommand.java @@ -14,7 +14,7 @@ public final class GodModeCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } @@ -35,6 +35,6 @@ public final class GodModeCommand implements CommandHandler { } targetPlayer.setGodmode(enabled); - CommandHandler.sendMessage(sender, translate("commands.godmode.success", (enabled ? translate("commands.status.enabled") : translate("commands.status.disabled")), targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate(sender, "commands.godmode.success", (enabled ? translate(sender, "commands.status.enabled") : translate(sender, "commands.status.disabled")), targetPlayer.getNickname())); } } diff --git a/src/main/java/emu/grasscutter/command/commands/HealCommand.java b/src/main/java/emu/grasscutter/command/commands/HealCommand.java index 78ff14405..0eb92356f 100644 --- a/src/main/java/emu/grasscutter/command/commands/HealCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/HealCommand.java @@ -17,7 +17,7 @@ public final class HealCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } @@ -32,6 +32,6 @@ public final class HealCommand implements CommandHandler { entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); } }); - CommandHandler.sendMessage(sender, translate("commands.heal.success")); + CommandHandler.sendMessage(sender, translate(sender, "commands.heal.success")); } } diff --git a/src/main/java/emu/grasscutter/command/commands/HelpCommand.java b/src/main/java/emu/grasscutter/command/commands/HelpCommand.java index 8a222f7a6..fc94426d7 100644 --- a/src/main/java/emu/grasscutter/command/commands/HelpCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/HelpCommand.java @@ -32,16 +32,16 @@ public final class HelpCommand implements CommandHandler { } else { String command = args.get(0); CommandHandler handler = CommandMap.getInstance().getHandler(command); - StringBuilder builder = new StringBuilder(player == null ? "\n" + translate("commands.status.help") + " - " : translate("commands.status.help") + " - ").append(command).append(": \n"); + StringBuilder builder = new StringBuilder(player == null ? "\n" + translate(player, "commands.status.help") + " - " : translate(player, "commands.status.help") + " - ").append(command).append(": \n"); if (handler == null) { - builder.append(translate("commands.generic.command_exist_error")); + builder.append(translate(player, "commands.generic.command_exist_error")); } else { Command annotation = handler.getClass().getAnnotation(Command.class); - builder.append(" ").append(translate(annotation.description())).append("\n"); - builder.append(translate("commands.help.usage")).append(annotation.usage()); + builder.append(" ").append(translate(player, annotation.description())).append("\n"); + builder.append(translate(player, "commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { - builder.append("\n").append(translate("commands.help.aliases")); + builder.append("\n").append(translate(player, "commands.help.aliases")); for (String alias : annotation.aliases()) { builder.append(alias).append(" "); } @@ -57,13 +57,13 @@ public final class HelpCommand implements CommandHandler { void SendAllHelpMessage(Player player, List annotations) { if (player == null) { - StringBuilder builder = new StringBuilder("\n" + translate("commands.help.available_commands") + "\n"); + StringBuilder builder = new StringBuilder("\n" + translate(player, "commands.help.available_commands") + "\n"); annotations.forEach(annotation -> { builder.append(annotation.label()).append("\n"); - builder.append(" ").append(translate(annotation.description())).append("\n"); - builder.append(translate("commands.help.usage")).append(annotation.usage()); + builder.append(" ").append(translate(player, annotation.description())).append("\n"); + builder.append(translate(player, "commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { - builder.append("\n").append(translate("commands.help.aliases")); + builder.append("\n").append(translate(player, "commands.help.aliases")); for (String alias : annotation.aliases()) { builder.append(alias).append(" "); } @@ -74,13 +74,13 @@ public final class HelpCommand implements CommandHandler { CommandHandler.sendMessage(null, builder.toString()); } else { - CommandHandler.sendMessage(player, translate("commands.help.available_commands")); + CommandHandler.sendMessage(player, translate(player, "commands.help.available_commands")); annotations.forEach(annotation -> { StringBuilder builder = new StringBuilder(annotation.label()).append("\n"); - builder.append(" ").append(translate(annotation.description())).append("\n"); - builder.append(translate("commands.help.usage")).append(annotation.usage()); + builder.append(" ").append(translate(player, annotation.description())).append("\n"); + builder.append(translate(player, "commands.help.usage")).append(annotation.usage()); if (annotation.aliases().length >= 1) { - builder.append("\n").append(translate("commands.help.aliases")); + builder.append("\n").append(translate(player, "commands.help.aliases")); for (String alias : annotation.aliases()) { builder.append(alias).append(" "); } diff --git a/src/main/java/emu/grasscutter/command/commands/KickCommand.java b/src/main/java/emu/grasscutter/command/commands/KickCommand.java index 9741226e7..57837666c 100644 --- a/src/main/java/emu/grasscutter/command/commands/KickCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KickCommand.java @@ -14,16 +14,16 @@ public final class KickCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } if (sender != null) { - CommandHandler.sendMessage(sender, translate("commands.kick.player_kick_player", + CommandHandler.sendMessage(sender, translate(sender, "commands.kick.player_kick_player", Integer.toString(sender.getAccount().getPlayerUid()), sender.getAccount().getUsername(), Integer.toString(targetPlayer.getUid()), targetPlayer.getAccount().getUsername())); } else { - CommandHandler.sendMessage(null, translate("commands.kick.server_kick_player", Integer.toString(targetPlayer.getUid()), targetPlayer.getAccount().getUsername())); + CommandHandler.sendMessage(null, translate(sender, "commands.kick.server_kick_player", Integer.toString(targetPlayer.getUid()), targetPlayer.getAccount().getUsername())); } targetPlayer.getSession().close(); diff --git a/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java b/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java index 853395fe6..a803f7d3a 100644 --- a/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KillAllCommand.java @@ -18,7 +18,7 @@ public final class KillAllCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } @@ -31,14 +31,14 @@ public final class KillAllCommand implements CommandHandler { scene = targetPlayer.getWorld().getSceneById(Integer.parseInt(args.get(0))); break; default: - CommandHandler.sendMessage(sender, translate("commands.kill.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.kill.usage")); return; } } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.execution.argument_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error")); } if (scene == null) { - CommandHandler.sendMessage(sender, translate("commands.kill.scene_not_found_in_player_world")); + CommandHandler.sendMessage(sender, translate(sender, "commands.kill.scene_not_found_in_player_world")); return; } @@ -48,6 +48,6 @@ public final class KillAllCommand implements CommandHandler { .filter(entity -> entity instanceof EntityMonster) .toList(); toKill.forEach(entity -> sceneF.killEntity(entity, 0)); - CommandHandler.sendMessage(sender, translate("commands.kill.kill_monsters_in_scene", Integer.toString(toKill.size()), Integer.toString(scene.getId()))); + CommandHandler.sendMessage(sender, translate(sender, "commands.kill.kill_monsters_in_scene", Integer.toString(toKill.size()), Integer.toString(scene.getId()))); } } diff --git a/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java b/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java index 99a57e122..b0f5c3603 100644 --- a/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/KillCharacterCommand.java @@ -19,7 +19,7 @@ public final class KillCharacterCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } @@ -32,6 +32,6 @@ public final class KillCharacterCommand implements CommandHandler { targetPlayer.getScene().removeEntity(entity); entity.onDeath(0); - CommandHandler.sendMessage(sender, translate("commands.killCharacter.success", targetPlayer.getNickname())); + CommandHandler.sendMessage(sender, translate(sender, "commands.killCharacter.success", targetPlayer.getNickname())); } } diff --git a/src/main/java/emu/grasscutter/command/commands/ListCommand.java b/src/main/java/emu/grasscutter/command/commands/ListCommand.java index 53a274e52..5c35874cb 100644 --- a/src/main/java/emu/grasscutter/command/commands/ListCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ListCommand.java @@ -22,7 +22,7 @@ public final class ListCommand implements CommandHandler { needUID = args.get(0).equals("uid"); } - CommandHandler.sendMessage(sender, translate("commands.list.success", Integer.toString(playersMap.size()))); + CommandHandler.sendMessage(sender, translate(sender, "commands.list.success", Integer.toString(playersMap.size()))); if (playersMap.size() != 0) { StringBuilder playerSet = new StringBuilder(); diff --git a/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java b/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java index 4b945b3d1..fdcda5b95 100644 --- a/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/PermissionCommand.java @@ -16,12 +16,12 @@ public final class PermissionCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } if (args.size() != 2) { - CommandHandler.sendMessage(sender, translate("commands.permission.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.permission.usage")); return; } @@ -30,26 +30,26 @@ public final class PermissionCommand implements CommandHandler { Account account = targetPlayer.getAccount(); if (account == null) { - CommandHandler.sendMessage(sender, translate("commands.permission.account_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.permission.account_error")); return; } switch (action) { default: - CommandHandler.sendMessage(sender, translate("commands.permission.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.permission.usage")); break; case "add": if (account.addPermission(permission)) { - CommandHandler.sendMessage(sender, translate("commands.permission.add")); - } else CommandHandler.sendMessage(sender, translate("commands.permission.has_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.permission.add")); + } else CommandHandler.sendMessage(sender, translate(sender, "commands.permission.has_error")); break; case "remove": if (account.removePermission(permission)) { - CommandHandler.sendMessage(sender, translate("commands.permission.remove")); - } else CommandHandler.sendMessage(sender, translate("commands.permission.not_have_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.permission.remove")); + } else CommandHandler.sendMessage(sender, translate(sender, "commands.permission.not_have_error")); break; } account.save(); } -} \ No newline at end of file +} diff --git a/src/main/java/emu/grasscutter/command/commands/PositionCommand.java b/src/main/java/emu/grasscutter/command/commands/PositionCommand.java index b5a250af6..530d44ae0 100644 --- a/src/main/java/emu/grasscutter/command/commands/PositionCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/PositionCommand.java @@ -15,12 +15,12 @@ public final class PositionCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } Position pos = targetPlayer.getPos(); - CommandHandler.sendMessage(sender, translate("commands.position.success", + CommandHandler.sendMessage(sender, translate(sender, "commands.position.success", Float.toString(pos.getX()), Float.toString(pos.getY()), Float.toString(pos.getZ()), Integer.toString(targetPlayer.getSceneId()))); } diff --git a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java index 29eb93d0d..9414a89c4 100644 --- a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java @@ -14,7 +14,7 @@ public final class ReloadCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { - CommandHandler.sendMessage(sender, translate("commands.reload.reload_start")); + CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_start")); Grasscutter.loadConfig(); Grasscutter.loadLanguage(); @@ -23,6 +23,6 @@ public final class ReloadCommand implements CommandHandler { Grasscutter.getGameServer().getShopManager().load(); Grasscutter.getDispatchServer().loadQueries(); - CommandHandler.sendMessage(sender, translate("commands.reload.reload_done")); + CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_done")); } } diff --git a/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java b/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java index e8229eaf5..3c962b950 100644 --- a/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ResetConstCommand.java @@ -17,13 +17,13 @@ public final class ResetConstCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } if (args.size() > 0 && args.get(0).equalsIgnoreCase("all")) { targetPlayer.getAvatars().forEach(this::resetConstellation); - CommandHandler.sendMessage(sender, translate("commands.resetConst.reset_all")); + CommandHandler.sendMessage(sender, translate(sender, "commands.resetConst.reset_all")); } else { EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity(); if (entity == null) { @@ -33,7 +33,7 @@ public final class ResetConstCommand implements CommandHandler { Avatar avatar = entity.getAvatar(); this.resetConstellation(avatar); - CommandHandler.sendMessage(sender, translate("commands.resetConst.success", avatar.getAvatarData().getName())); + CommandHandler.sendMessage(sender, translate(sender, "commands.resetConst.success", avatar.getAvatarData().getName())); } } @@ -43,4 +43,4 @@ public final class ResetConstCommand implements CommandHandler { avatar.recalcStats(); avatar.save(); } -} \ No newline at end of file +} diff --git a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java index bf5e2a4a6..d2b910811 100644 --- a/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ResetShopLimitCommand.java @@ -15,12 +15,12 @@ public final class ResetShopLimitCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } targetPlayer.getShopLimit().forEach(x -> x.setNextRefreshTime(0)); targetPlayer.save(); - CommandHandler.sendMessage(sender, translate("commands.status.success")); + CommandHandler.sendMessage(sender, translate(sender, "commands.status.success")); } } diff --git a/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java b/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java index 69aafa20b..21d9e64d9 100644 --- a/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SendMailCommand.java @@ -39,7 +39,7 @@ public final class SendMailCommand implements CommandHandler { MailBuilder mailBuilder; switch (args.get(0).toLowerCase()) { case "help" -> { - CommandHandler.sendMessage(sender, translate(this.getClass().getAnnotation(Command.class).description()) + "\nUsage: " + this.getClass().getAnnotation(Command.class).usage()); + CommandHandler.sendMessage(sender, translate(sender, this.getClass().getAnnotation(Command.class).description()) + "\nUsage: " + this.getClass().getAnnotation(Command.class).usage()); return; } case "all" -> mailBuilder = new MailBuilder(true, new Mail()); @@ -47,16 +47,16 @@ public final class SendMailCommand implements CommandHandler { if (DatabaseHelper.getPlayerById(Integer.parseInt(args.get(0))) != null) { mailBuilder = new MailBuilder(Integer.parseInt(args.get(0)), new Mail()); } else { - CommandHandler.sendMessage(sender, translate("commands.sendMail.user_not_exist", args.get(0))); + CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.user_not_exist", args.get(0))); return; } } } mailBeingConstructed.put(senderId, mailBuilder); - CommandHandler.sendMessage(sender, translate("commands.sendMail.start_composition")); + CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.start_composition")); } - case 2 -> CommandHandler.sendMessage(sender, translate("commands.sendMail.templates")); - default -> CommandHandler.sendMessage(sender, translate("commands.sendMail.invalid_arguments")); + case 2 -> CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.templates")); + default -> CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.invalid_arguments")); } } else { MailBuilder mailBuilder = mailBeingConstructed.get(senderId); @@ -65,28 +65,28 @@ public final class SendMailCommand implements CommandHandler { switch (args.get(0).toLowerCase()) { case "stop" -> { mailBeingConstructed.remove(senderId); - CommandHandler.sendMessage(sender, translate("commands.sendMail.sendCancel")); + CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.sendCancel")); return; } case "finish" -> { if (mailBuilder.constructionStage == 3) { if (!mailBuilder.sendToAll) { Grasscutter.getGameServer().getPlayerByUid(mailBuilder.recipient, true).sendMail(mailBuilder.mail); - CommandHandler.sendMessage(sender, translate("commands.sendMail.send_done", Integer.toString(mailBuilder.recipient))); + CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.send_done", Integer.toString(mailBuilder.recipient))); } else { for (Player player : DatabaseHelper.getAllPlayers()) { Grasscutter.getGameServer().getPlayerByUid(player.getUid(), true).sendMail(mailBuilder.mail); } - CommandHandler.sendMessage(sender, translate("commands.sendMail.send_all_done")); + CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.send_all_done")); } mailBeingConstructed.remove(senderId); } else { - CommandHandler.sendMessage(sender, translate("commands.sendMail.not_composition_end", getConstructionArgs(mailBuilder.constructionStage))); + CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.not_composition_end", getConstructionArgs(mailBuilder.constructionStage, sender))); } return; } case "help" -> { - CommandHandler.sendMessage(sender, translate("commands.sendMail.please_use", getConstructionArgs(mailBuilder.constructionStage))); + CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.please_use", getConstructionArgs(mailBuilder.constructionStage, sender))); return; } default -> { @@ -94,19 +94,19 @@ public final class SendMailCommand implements CommandHandler { case 0 -> { String title = String.join(" ", args.subList(0, args.size())); mailBuilder.mail.mailContent.title = title; - CommandHandler.sendMessage(sender, translate("commands.sendMail.set_title", title)); + CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.set_title", title)); mailBuilder.constructionStage++; } case 1 -> { String contents = String.join(" ", args.subList(0, args.size())); mailBuilder.mail.mailContent.content = contents; - CommandHandler.sendMessage(sender, translate("commands.sendMail.set_contents", contents)); + CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.set_contents", contents)); mailBuilder.constructionStage++; } case 2 -> { String msgSender = String.join(" ", args.subList(0, args.size())); mailBuilder.mail.mailContent.sender = msgSender; - CommandHandler.sendMessage(sender, translate("commands.sendMail.set_message_sender", msgSender)); + CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.set_message_sender", msgSender)); mailBuilder.constructionStage++; } case 3 -> { @@ -119,21 +119,21 @@ public final class SendMailCommand implements CommandHandler { try { refinement = Integer.parseInt(args.get(3)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemRefinement")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemRefinement")); return; } // Fallthrough case 3: // [amount] [level] try { lvl = Integer.parseInt(args.get(2)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemLevel")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemLevel")); return; } // Fallthrough case 2: // [amount] try { amount = Integer.parseInt(args.get(1)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.generic.invalid.amount")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.amount")); return; } // Fallthrough case 1: // @@ -141,33 +141,33 @@ public final class SendMailCommand implements CommandHandler { item = Integer.parseInt(args.get(0)); } catch (NumberFormatException ignored) { // TODO: Parse from item name using GM Handbook. - CommandHandler.sendMessage(sender, translate("commands.generic.invalid.itemId")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.itemId")); return; } break; default: // *No args* - CommandHandler.sendMessage(sender, translate("commands.give.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.give.usage")); return; } mailBuilder.mail.itemList.add(new Mail.MailItem(item, amount, lvl)); - CommandHandler.sendMessage(sender, translate("commands.sendMail.send", Integer.toString(amount), Integer.toString(item), Integer.toString(lvl))); + CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.send", Integer.toString(amount), Integer.toString(item), Integer.toString(lvl))); } } } } } else { - CommandHandler.sendMessage(sender, translate("commands.sendMail.invalid_arguments_please_use", getConstructionArgs(mailBuilder.constructionStage))); + CommandHandler.sendMessage(sender, translate(sender, "commands.sendMail.invalid_arguments_please_use", getConstructionArgs(mailBuilder.constructionStage, sender))); } } } - private String getConstructionArgs(int stage) { + private String getConstructionArgs(int stage, Player sender) { return switch(stage) { - case 0 -> translate("commands.sendMail.title"); - case 1 -> translate("commands.sendMail.message"); - case 2 -> translate("commands.sendMail.sender"); - case 3 -> translate("commands.sendMail.arguments"); - default -> translate("commands.sendMail.error", Integer.toString(stage)); + case 0 -> translate(sender, "commands.sendMail.title"); + case 1 -> translate(sender, "commands.sendMail.message"); + case 2 -> translate(sender, "commands.sendMail.sender"); + case 3 -> translate(sender, "commands.sendMail.arguments"); + default -> translate(sender, "commands.sendMail.error", Integer.toString(stage)); }; } diff --git a/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java b/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java index befbf4f00..2e6feb96d 100644 --- a/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SendMessageCommand.java @@ -15,16 +15,16 @@ public final class SendMessageCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } if (args.size() == 0) { - CommandHandler.sendMessage(null, translate("commands.sendMessage.usage")); + CommandHandler.sendMessage(null, translate(sender, "commands.sendMessage.usage")); return; } String message = String.join(" ", args); CommandHandler.sendMessage(targetPlayer, message); - CommandHandler.sendMessage(sender, translate("commands.sendMessage.success")); + CommandHandler.sendMessage(sender, translate(sender, "commands.sendMessage.success")); } -} \ No newline at end of file +} diff --git a/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java b/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java index cf356d06b..2f838fb1a 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetFetterLevelCommand.java @@ -18,19 +18,19 @@ public final class SetFetterLevelCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } if (args.size() != 1) { - CommandHandler.sendMessage(sender, translate("commands.setFetterLevel.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.setFetterLevel.usage")); return; } try { int fetterLevel = Integer.parseInt(args.get(0)); if (fetterLevel < 0 || fetterLevel > 10) { - CommandHandler.sendMessage(sender, translate("commands.setFetterLevel.range_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.setFetterLevel.range_error")); return; } Avatar avatar = targetPlayer.getTeamManager().getCurrentAvatarEntity().getAvatar(); @@ -42,9 +42,9 @@ public final class SetFetterLevelCommand implements CommandHandler { avatar.save(); targetPlayer.sendPacket(new PacketAvatarFetterDataNotify(avatar)); - CommandHandler.sendMessage(sender, translate("commands.setFetterLevel.success", fetterLevel)); + CommandHandler.sendMessage(sender, translate(sender, "commands.setFetterLevel.success", fetterLevel)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.setFetterLevel.level_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.setFetterLevel.level_error")); } } diff --git a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java index a0572d2c2..b51b09197 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetStatsCommand.java @@ -175,13 +175,13 @@ public final class SetStatsCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { - String syntax = sender == null ? translate("commands.setStats.usage_console") : translate("commands.setStats.usage_ingame"); - String usage = syntax + translate("commands.setStats.help_message"); + String syntax = sender == null ? translate(sender, "commands.setStats.usage_console") : translate(sender, "commands.setStats.usage_ingame"); + String usage = syntax + translate(sender, "commands.setStats.help_message"); String statStr; String valueStr; if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } @@ -203,7 +203,7 @@ public final class SetStatsCommand implements CommandHandler { value = Float.parseFloat(valueStr); } } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.setStats.value_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.setStats.value_error")); return; } @@ -217,10 +217,10 @@ public final class SetStatsCommand implements CommandHandler { valueStr = String.format("%.0f", value); } if (targetPlayer == sender) { - CommandHandler.sendMessage(sender, translate("commands.setStats.set_self", stat.name, valueStr)); + CommandHandler.sendMessage(sender, translate(sender, "commands.setStats.set_self", stat.name, valueStr)); } else { String uidStr = targetPlayer.getAccount().getId(); - CommandHandler.sendMessage(sender, translate("commands.setStats.set_self", stat.name, uidStr, valueStr)); + CommandHandler.sendMessage(sender, translate(sender, "commands.setStats.set_self", stat.name, uidStr, valueStr)); } } else { CommandHandler.sendMessage(sender, usage); diff --git a/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java b/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java index aa773159e..d09c00a91 100644 --- a/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SetWorldLevelCommand.java @@ -16,19 +16,19 @@ public final class SetWorldLevelCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } if (args.size() < 1) { - CommandHandler.sendMessage(sender, translate("commands.setWorldLevel.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.setWorldLevel.usage")); return; } try { int level = Integer.parseInt(args.get(0)); if (level > 8 || level < 0) { - CommandHandler.sendMessage(sender, translate("commands.setWorldLevel.value_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.setWorldLevel.value_error")); return; } @@ -36,9 +36,9 @@ public final class SetWorldLevelCommand implements CommandHandler { targetPlayer.getWorld().setWorldLevel(level); targetPlayer.setWorldLevel(level); - CommandHandler.sendMessage(sender, translate("commands.setWorldLevel.success", Integer.toString(level))); + CommandHandler.sendMessage(sender, translate(sender, "commands.setWorldLevel.success", Integer.toString(level))); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(null, translate("commands.setWorldLevel.invalid_world_level")); + CommandHandler.sendMessage(null, translate(sender, "commands.setWorldLevel.invalid_world_level")); } } } diff --git a/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java b/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java index e3193c638..24a6bc174 100644 --- a/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/SpawnCommand.java @@ -28,7 +28,7 @@ public final class SpawnCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } @@ -40,23 +40,23 @@ public final class SpawnCommand implements CommandHandler { try { level = Integer.parseInt(args.get(2)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.execution.argument_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error")); } // Fallthrough case 2: try { amount = Integer.parseInt(args.get(1)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.generic.invalid.amount")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.amount")); } // Fallthrough case 1: try { id = Integer.parseInt(args.get(0)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.generic.invalid.entityId")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.entityId")); } break; default: - CommandHandler.sendMessage(sender, translate("commands.spawn.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.spawn.usage")); return; } @@ -64,7 +64,7 @@ public final class SpawnCommand implements CommandHandler { GadgetData gadgetData = GameData.getGadgetDataMap().get(id); ItemData itemData = GameData.getItemDataMap().get(id); if (monsterData == null && gadgetData == null && itemData == null) { - CommandHandler.sendMessage(sender, translate("commands.generic.invalid.entityId")); + CommandHandler.sendMessage(sender, translate(sender, "commands.generic.invalid.entityId")); return; } Scene scene = targetPlayer.getScene(); @@ -100,7 +100,7 @@ public final class SpawnCommand implements CommandHandler { scene.addEntity(entity); } - CommandHandler.sendMessage(sender, translate("commands.spawn.success", Integer.toString(amount), Integer.toString(id))); + CommandHandler.sendMessage(sender, translate(sender, "commands.spawn.success", Integer.toString(amount), Integer.toString(id))); } private Position GetRandomPositionInCircle(Position origin, double radius){ diff --git a/src/main/java/emu/grasscutter/command/commands/StopCommand.java b/src/main/java/emu/grasscutter/command/commands/StopCommand.java index 129b27b24..d1fa0fe75 100644 --- a/src/main/java/emu/grasscutter/command/commands/StopCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/StopCommand.java @@ -14,9 +14,9 @@ public final class StopCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { - CommandHandler.sendMessage(null, translate("commands.stop.success")); + CommandHandler.sendMessage(null, translate(sender, "commands.stop.success")); for (Player p : Grasscutter.getGameServer().getPlayers().values()) { - CommandHandler.sendMessage(p, translate("commands.stop.success")); + CommandHandler.sendMessage(p, translate(sender, "commands.stop.success")); } System.exit(1000); diff --git a/src/main/java/emu/grasscutter/command/commands/TalentCommand.java b/src/main/java/emu/grasscutter/command/commands/TalentCommand.java index 40ac11b50..6cb687efe 100644 --- a/src/main/java/emu/grasscutter/command/commands/TalentCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TalentCommand.java @@ -19,7 +19,7 @@ public final class TalentCommand implements CommandHandler { private void setTalentLevel(Player sender, Player player, Avatar avatar, int talentId, int talentLevel) { int oldLevel = avatar.getSkillLevelMap().get(talentId); if (talentLevel < 0 || talentLevel > 15) { - CommandHandler.sendMessage(sender, translate("commands.talent.lower_16")); + CommandHandler.sendMessage(sender, translate(sender, "commands.talent.lower_16")); return; } @@ -40,20 +40,20 @@ public final class TalentCommand implements CommandHandler { } else if (talentId == depot.getEnergySkill()) { successMessage = "commands.talent.set_q"; } - CommandHandler.sendMessage(sender, translate(successMessage, talentLevel)); + CommandHandler.sendMessage(sender, translate(sender, successMessage, talentLevel)); } @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } if (args.size() < 1){ - CommandHandler.sendMessage(sender, translate("commands.talent.usage_1")); - CommandHandler.sendMessage(sender, translate("commands.talent.usage_2")); - CommandHandler.sendMessage(sender, translate("commands.talent.usage_3")); + CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_1")); + CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_2")); + CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_3")); return; } @@ -62,15 +62,15 @@ public final class TalentCommand implements CommandHandler { String cmdSwitch = args.get(0); switch (cmdSwitch) { default -> { - CommandHandler.sendMessage(sender, translate("commands.talent.usage_1")); - CommandHandler.sendMessage(sender, translate("commands.talent.usage_2")); - CommandHandler.sendMessage(sender, translate("commands.talent.usage_3")); + CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_1")); + CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_2")); + CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_3")); return; } case "set" -> { if (args.size() < 3) { - CommandHandler.sendMessage(sender, translate("commands.talent.usage_1")); - CommandHandler.sendMessage(sender, translate("commands.talent.usage_3")); + CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_1")); + CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_3")); return; } try { @@ -78,13 +78,13 @@ public final class TalentCommand implements CommandHandler { int newLevel = Integer.parseInt(args.get(2)); setTalentLevel(sender, targetPlayer, avatar, skillId, newLevel); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.talent.invalid_skill_id")); + CommandHandler.sendMessage(sender, translate(sender, "commands.talent.invalid_skill_id")); return; } } case "n", "e", "q" -> { if (args.size() < 2) { - CommandHandler.sendMessage(sender, translate("commands.talent.usage_2")); + CommandHandler.sendMessage(sender, translate(sender, "commands.talent.usage_2")); return; } AvatarSkillDepotData SkillDepot = avatar.getData().getSkillDepot(); @@ -97,7 +97,7 @@ public final class TalentCommand implements CommandHandler { int newLevel = Integer.parseInt(args.get(1)); setTalentLevel(sender, targetPlayer, avatar, skillId, newLevel); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.talent.invalid_level")); + CommandHandler.sendMessage(sender, translate(sender, "commands.talent.invalid_level")); return; } } @@ -105,9 +105,9 @@ public final class TalentCommand implements CommandHandler { int skillIdNorAtk = avatar.getData().getSkillDepot().getSkills().get(0); int skillIdE = avatar.getData().getSkillDepot().getSkills().get(1); int skillIdQ = avatar.getData().getSkillDepot().getEnergySkill(); - CommandHandler.sendMessage(sender, translate("commands.talent.normal_attack_id", Integer.toString(skillIdNorAtk))); - CommandHandler.sendMessage(sender, translate("commands.talent.e_skill_id", Integer.toString(skillIdE))); - CommandHandler.sendMessage(sender, translate("commands.talent.q_skill_id", Integer.toString(skillIdQ))); + CommandHandler.sendMessage(sender, translate(sender, "commands.talent.normal_attack_id", Integer.toString(skillIdNorAtk))); + CommandHandler.sendMessage(sender, translate(sender, "commands.talent.e_skill_id", Integer.toString(skillIdE))); + CommandHandler.sendMessage(sender, translate(sender, "commands.talent.q_skill_id", Integer.toString(skillIdQ))); } } } diff --git a/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java b/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java index bfa0ac821..23f9c6d40 100644 --- a/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TeleportAllCommand.java @@ -16,12 +16,12 @@ public final class TeleportAllCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } if (!targetPlayer.getWorld().isMultiplayer()) { - CommandHandler.sendMessage(sender, translate("commands.teleportAll.error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.teleportAll.error")); return; } @@ -33,6 +33,6 @@ public final class TeleportAllCommand implements CommandHandler { player.getWorld().transferPlayerToScene(player, targetPlayer.getSceneId(), pos); } - CommandHandler.sendMessage(sender, translate("commands.teleportAll.success")); + CommandHandler.sendMessage(sender, translate(sender, "commands.teleportAll.success")); } } diff --git a/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java b/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java index 8a9fb9948..62827d86c 100644 --- a/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/TeleportCommand.java @@ -27,7 +27,7 @@ public final class TeleportCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } @@ -42,7 +42,7 @@ public final class TeleportCommand implements CommandHandler { try { sceneId = Integer.parseInt(args.get(3)); }catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.execution.argument_error")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.argument_error")); } // Fallthrough case 3: try { @@ -50,20 +50,20 @@ public final class TeleportCommand implements CommandHandler { y = parseRelative(args.get(1), y); z = parseRelative(args.get(2), z); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.teleport.invalid_position")); + CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.invalid_position")); } break; default: - CommandHandler.sendMessage(sender, translate("commands.teleport.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.usage")); return; } Position target_pos = new Position(x, y, z); boolean result = targetPlayer.getWorld().transferPlayerToScene(targetPlayer, sceneId, target_pos); if (!result) { - CommandHandler.sendMessage(sender, translate("commands.teleport.invalid_position")); + CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.invalid_position")); } else { - CommandHandler.sendMessage(sender, translate("commands.teleport.success", + CommandHandler.sendMessage(sender, translate(sender, "commands.teleport.success", targetPlayer.getNickname(), Float.toString(x), Float.toString(y), Float.toString(z), Integer.toString(sceneId)) ); diff --git a/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java b/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java index bd7b8bc1f..c2b67209c 100644 --- a/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/UnlockTowerCommand.java @@ -21,7 +21,7 @@ public class UnlockTowerCommand implements CommandHandler { unlockFloor(sender, sender.getServer().getTowerScheduleManager() .getScheduleFloors()); - CommandHandler.sendMessage(sender, translate("commands.unlocktower.success")); + CommandHandler.sendMessage(sender, translate(sender, "commands.unlocktower.success")); } public void unlockFloor(Player player, List floors){ diff --git a/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java b/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java index 0edbd8482..7ad347465 100644 --- a/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/WeatherCommand.java @@ -17,7 +17,7 @@ public final class WeatherCommand implements CommandHandler { @Override public void execute(Player sender, Player targetPlayer, List args) { if (targetPlayer == null) { - CommandHandler.sendMessage(sender, translate("commands.execution.need_target")); + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); return; } @@ -28,17 +28,17 @@ public final class WeatherCommand implements CommandHandler { try { climateId = Integer.parseInt(args.get(1)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.weather.invalid_id")); + CommandHandler.sendMessage(sender, translate(sender, "commands.weather.invalid_id")); } case 1: try { weatherId = Integer.parseInt(args.get(0)); } catch (NumberFormatException ignored) { - CommandHandler.sendMessage(sender, translate("commands.weather.invalid_id")); + CommandHandler.sendMessage(sender, translate(sender, "commands.weather.invalid_id")); } break; default: - CommandHandler.sendMessage(sender, translate("commands.weather.usage")); + CommandHandler.sendMessage(sender, translate(sender, "commands.weather.usage")); return; } @@ -47,6 +47,6 @@ public final class WeatherCommand implements CommandHandler { targetPlayer.getScene().setWeather(weatherId); targetPlayer.getScene().setClimate(climate); targetPlayer.getScene().broadcastPacket(new PacketSceneAreaWeatherNotify(targetPlayer)); - CommandHandler.sendMessage(sender, translate("commands.weather.success", Integer.toString(weatherId), Integer.toString(climateId))); + CommandHandler.sendMessage(sender, translate(sender, "commands.weather.success", Integer.toString(weatherId), Integer.toString(climateId))); } } diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java index 7e8baa291..821dea80b 100644 --- a/src/main/java/emu/grasscutter/game/Account.java +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -8,6 +8,7 @@ import emu.grasscutter.utils.Utils; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import org.bson.Document; @@ -28,10 +29,12 @@ public class Account { private String token; private String sessionKey; // Session token for dispatch server private List permissions; + private Locale locale; @Deprecated public Account() { this.permissions = new ArrayList<>(); + this.locale = Grasscutter.getConfig().LocaleLanguage; } public String getId() { @@ -96,6 +99,14 @@ public class Account { return this.sessionKey; } + public Locale getLocale() { + return locale; + } + + public void setLocale(Locale locale) { + this.locale = locale; + } + /** * The collection of a player's permissions. */ @@ -166,5 +177,10 @@ public class Account { if (!document.containsKey("permissions")) { this.addPermission("*"); } + + // Set account default language as server default language + if (!document.containsKey("locale")) { + this.locale = Grasscutter.getConfig().LocaleLanguage; + } } } diff --git a/src/main/java/emu/grasscutter/utils/Language.java b/src/main/java/emu/grasscutter/utils/Language.java index 04bd352f7..57f211020 100644 --- a/src/main/java/emu/grasscutter/utils/Language.java +++ b/src/main/java/emu/grasscutter/utils/Language.java @@ -3,6 +3,8 @@ package emu.grasscutter.utils; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.Account; +import emu.grasscutter.game.player.Player; import javax.annotation.Nullable; import java.io.InputStream; @@ -11,6 +13,7 @@ import java.util.Map; public final class Language { private final JsonObject languageData; + private final String languageCode; private final Map cachedTranslations = new ConcurrentHashMap<>(); private static final Map cachedLanguages = new ConcurrentHashMap<>(); @@ -24,8 +27,21 @@ public final class Language { return cachedLanguages.get(langCode); } - var languageInst = new Language(langCode + ".json", Utils.getLanguageCode(Grasscutter.getConfig().DefaultLanguage) + ".json"); - cachedLanguages.put(langCode, languageInst); + var fallbackLanguageCode = Utils.getLanguageCode(Grasscutter.getConfig().DefaultLanguage); + var descripter = getLanguageFileStreamDescripter(langCode, fallbackLanguageCode); + var actualLanguageCode = descripter.getLanguageCode(); + + Language languageInst = null; + + if (descripter.getLanguageFile() != null) { + languageInst = new Language(descripter); + cachedLanguages.put(actualLanguageCode, languageInst); + } + else { + languageInst = cachedLanguages.get(actualLanguageCode); + cachedLanguages.put(langCode, languageInst); + } + return languageInst; } @@ -47,34 +63,90 @@ public final class Language { } /** - * Reads a file and creates a language instance. - * @param fileName The name of the language file. - * @param fallback The name of the fallback language file. + * Returns the translated value from the key while substituting arguments. + * @param player Target player + * @param key The key of the translated value to return. + * @param args The arguments to substitute. + * @return A translated value with arguments substituted. */ - private Language(String fileName, String fallback) { - @Nullable JsonObject languageData = null; + public static String translate(Player player, String key, Object... args) { + if (player == null) { + return translate(key, args); + } - InputStream file = Grasscutter.class.getResourceAsStream("/languages/" + fileName); - if (file == null) { // Provided fallback language. - file = Grasscutter.class.getResourceAsStream("/languages/" + fallback); - Grasscutter.getLogger().warn("Failed to load language file: " + fileName + ", falling back to: " + fallback); - } - if(file == null) { // Fallback the fallback language. - file = Grasscutter.class.getResourceAsStream("/languages/en-US.json"); - Grasscutter.getLogger().warn("Failed to load language file: " + fallback + ", falling back to: en-US.json"); - } - if(file == null) - throw new RuntimeException("Unable to load the primary, fallback, and 'en-US' language files."); + var langCode = Utils.getLanguageCode(player.getAccount().getLocale()); + String translated = Grasscutter.getLanguage(langCode).get(key); try { - languageData = Grasscutter.getGsonFactory().fromJson(Utils.readFromInputStream(file), JsonObject.class); + return translated.formatted(args); } catch (Exception exception) { - Grasscutter.getLogger().warn("Failed to load language file: " + fileName, exception); + Grasscutter.getLogger().error("Failed to format string: " + key, exception); + return translated; + } + } + + /** + * get language code + */ + public String getLanguageCode() { + return languageCode; + } + + /** + * Reads a file and creates a language instance. + */ + private Language(InternalLanguageFileStreamDescripter descripter) { + @Nullable JsonObject languageData = null; + languageCode = descripter.getLanguageCode(); + + try { + languageData = Grasscutter.getGsonFactory().fromJson(Utils.readFromInputStream(descripter.getLanguageFile()), JsonObject.class); + } catch (Exception exception) { + Grasscutter.getLogger().warn("Failed to load language file: " + descripter.getLanguageCode(), exception); } this.languageData = languageData; } + /** + * create a InternalLanguageFileStreamDescripter + * @param languageCode The name of the language code. + * @param fallbackLanguageCode The name of the fallback language code. + */ + private static InternalLanguageFileStreamDescripter getLanguageFileStreamDescripter(String languageCode, String fallbackLanguageCode) { + var fileName = languageCode + ".json"; + var fallback = fallbackLanguageCode + ".json"; + + String actualLanguageCode = languageCode; + if (cachedLanguages.containsKey(actualLanguageCode)) { + return new InternalLanguageFileStreamDescripter(actualLanguageCode, null); + } + InputStream file = Grasscutter.class.getResourceAsStream("/languages/" + fileName); + + if (file == null) { // Provided fallback language. + actualLanguageCode = fallbackLanguageCode; + if (cachedLanguages.containsKey(actualLanguageCode)) { + return new InternalLanguageFileStreamDescripter(actualLanguageCode, null); + } + file = Grasscutter.class.getResourceAsStream("/languages/" + fallback); + Grasscutter.getLogger().warn("Failed to load language file: " + fileName + ", falling back to: " + fallback); + } + + if(file == null) { // Fallback the fallback language. + actualLanguageCode = "en-US"; + if (cachedLanguages.containsKey(actualLanguageCode)) { + return new InternalLanguageFileStreamDescripter(actualLanguageCode, null); + } + file = Grasscutter.class.getResourceAsStream("/languages/en-US.json"); + Grasscutter.getLogger().warn("Failed to load language file: " + fallback + ", falling back to: en-US.json"); + } + + if(file == null) + throw new RuntimeException("Unable to load the primary, fallback, and 'en-US' language files."); + + return new InternalLanguageFileStreamDescripter(actualLanguageCode, file); + } + /** * Returns the value (as a string) from a nested key. * @param key The key to look for. @@ -107,4 +179,22 @@ public final class Language { this.cachedTranslations.put(key, result); return result; } + + private static class InternalLanguageFileStreamDescripter { + private String languageCode; + private InputStream languageFile; + + public InternalLanguageFileStreamDescripter(String languageCode, InputStream languageFile) { + this.languageCode = languageCode; + this.languageFile = languageFile; + } + + public String getLanguageCode() { + return languageCode; + } + + public InputStream getLanguageFile() { + return languageFile; + } + } } diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 9e7271ea3..893f490af 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -188,6 +188,11 @@ "success": "Killed %s's current character.", "description": "Kills the players current character" }, + "language": { + "current_language": "current language is %s", + "language_changed": "language changed to %s", + "description": "display or change current language" + }, "list": { "success": "There are %s player(s) online:", "description": "List online players" diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index f6a2de0ea..a326d4960 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -188,6 +188,11 @@ "success": "已杀死 %s 当前角色。", "description": "杀死当前角色" }, + "language": { + "current_language": "当前语言是: %s", + "language_changed": "语言切换至: %s", + "description": "显示或切换当前语言" + }, "list": { "success": "目前在线人数:%s", "description": "查看所有玩家" From 94409b8635abb753f9282ede8fd32e6d83fbe63b Mon Sep 17 00:00:00 2001 From: Secretboy <74841174+Secretboy-SMR@users.noreply.github.com> Date: Tue, 10 May 2022 21:35:37 +0800 Subject: [PATCH 071/312] fixed pr780, uploaded LanguageCommand.java (#782) * Fix the following issues: 1. HashMap non-thread-safe issus 2. Fix the same problem in pr621, but use a better implementation Add the following functions: 1. There is now a language cache inside getLanguage to prepare for different languages corresponding to different time zones where the accounts in the server are located * add /language command,each account has their own Locate * I forgot to git add...sorry,,this pr is to fix pr780, uploaded LanguageCommand.java --- .../command/commands/LanguageCommand.java | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/main/java/emu/grasscutter/command/commands/LanguageCommand.java diff --git a/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java b/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java new file mode 100644 index 000000000..e16455697 --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java @@ -0,0 +1,49 @@ +package emu.grasscutter.command.commands; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.Account; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.utils.Utils; + +import java.util.List; +import java.util.Locale; + +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "language", usage = "language [language code]", description = "commands.language.description", aliases = {"lang"}) +public final class LanguageCommand implements CommandHandler { + + @Override + public void execute(Player sender, Player targetPlayer, List args) { + if (args.isEmpty()) { + String curLangCode = null; + if (sender != null) { + curLangCode = Utils.getLanguageCode(sender.getAccount().getLocale()); + } + else { + curLangCode = Grasscutter.getLanguage().getLanguageCode(); + } + CommandHandler.sendMessage(sender, translate(sender, "commands.language.current_language", curLangCode)); + return; + } + + String langCode = args.get(0); + String actualLangCode = null; + if (sender != null) { + var locale = Locale.forLanguageTag(langCode); + actualLangCode = Utils.getLanguageCode(locale); + sender.getAccount().setLocale(locale); + return; + } + else { + var languageInst = Grasscutter.getLanguage(langCode); + actualLangCode = languageInst.getLanguageCode(); + Grasscutter.setLanguage(languageInst); + } + CommandHandler.sendMessage(sender, translate(sender, "commands.language.language_changed", actualLangCode)); + + } +} From f92e839ad14a537e3604b84b3939258638cb990e Mon Sep 17 00:00:00 2001 From: tester233 <105267106+tester233@users.noreply.github.com> Date: Tue, 10 May 2022 21:18:15 +0800 Subject: [PATCH 072/312] Improve text --- src/main/resources/languages/zh-CN.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index a326d4960..8d5b35137 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -14,7 +14,7 @@ "general_error": "[Dispatch] 加载 keystore 文件时发生错误!", "password_error": "[Dispatch] 加载 keystore 失败。正在尝试使用 keystore 默认密码...", "no_keystore_error": "[Dispatch] 未找到 SSL 证书!已降级到 HTTP 模式", - "default_password": "[Dispatch] keystore 默认密码加载成功。请考虑将 config.json 的默认密码设置为 123456" + "default_password": "[Dispatch] 成功加载 keystore 默认密码。请考虑将 config.json 的默认密码设置为 123456" }, "no_commands_error": "此命令不适用于 Dispatch-only 模式", "unhandled_request_error": "[Dispatch] 潜在的未处理请求:%s %s", @@ -181,21 +181,21 @@ "usage": "用法:killall [玩家UID] [场景ID]", "scene_not_found_in_player_world": "未在玩家世界中找到此场景", "kill_monsters_in_scene": "已杀死场景 %s 中的 %s 个怪物。", - "description": "杀死所有怪物" + "description": "杀死所有怪物。" }, "killCharacter": { "usage": "用法:/killcharacter [玩家ID]", "success": "已杀死 %s 当前角色。", - "description": "杀死当前角色" + "description": "杀死当前角色。" }, "language": { "current_language": "当前语言是: %s", "language_changed": "语言切换至: %s", - "description": "显示或切换当前语言" + "description": "显示或切换当前语言。" }, "list": { "success": "目前在线人数:%s", - "description": "查看所有玩家" + "description": "查看所有玩家。" }, "permission": { "usage": "用法:permission <用户名> <权限>", @@ -250,7 +250,7 @@ "sendMessage": { "usage": "用法:sendmessage <玩家> <消息>", "success": "消息已发送。", - "description": "向指定玩家发送消息" + "description": "向指定玩家发送消息。" }, "setFetterLevel": { "usage": "用法:setfetterlevel <好感度等级>", @@ -262,7 +262,7 @@ "setStats": { "usage_console": "用法:setstats|stats @ <属性> <数值>", "usage_ingame": "用法:setstats|stats [@UID] <属性> <数值>", - "help_message": "\n\t可更改的属性列表:hp (生命值)| maxhp (最大生命值) | def(防御力) | atk (攻击力)| em (元素精通) | er (元素充能效率) | crate(暴击率) | cdmg (暴击伤害)| cdr (冷却缩减) | heal(治疗加成)| heali (受治疗加成)| shield (护盾强效)| defi (无视防御)\n\t(续) 元素增伤:epyro (火) | ecryo (冰) | ehydro (水) | egeo (岩) | edendro (草) | eelectro (雷) | ephys (物理)\n\t(续) 元素抗性:respyro (火) | rescryo (冰) | reshydro (水) | resgeo (岩) | resdendro (草) | reselectro (雷) | resphys (物理)\n", + "help_message": "\n可更改的属性列表:hp (生命值)| maxhp (最大生命值) | def(防御力) | atk (攻击力)| em (元素精通) | er (元素充能效率) | crate(暴击率) | cdmg (暴击伤害)| cdr (冷却缩减) | heal(治疗加成)| heali (受治疗加成)| shield (护盾强效)| defi (无视防御)\n(续) 元素增伤:epyro (火) | ecryo (冰) | ehydro (水) | egeo (岩) | edendro (草) | eelectro (雷) | ephys (物理)\n(续) 元素抗性:respyro (火) | rescryo (冰) | reshydro (水) | resgeo (岩) | resdendro (草) | reselectro (雷) | resphys (物理)\n", "value_error": "无效的属性值。", "uid_error": "无效的UID。", "player_error": "玩家不存在或已离线。", @@ -285,7 +285,7 @@ }, "stop": { "success": "正在关闭服务器...", - "description": "停止服务器" + "description": "停止服务器。" }, "talent": { "usage_1": "设置天赋等级:/talent set <天赋ID> <数值>", @@ -324,7 +324,7 @@ "usage": "用法:weather <天气ID> [气候ID]", "success": "已更改天气为 %s,气候为 %s。", "invalid_id": "无效的天气ID。", - "description": "更改天气" + "description": "更改天气。" }, "drop": { "command_usage": "用法:drop <物品ID|物品名称> [数量]", @@ -342,10 +342,10 @@ }, "unlocktower": { "success": "解锁完成。", - "description": "解锁深境螺旋的所有层" + "description": "解锁深境螺旋的所有层。" }, "resetshop": { - "description": "重置商店时间" + "description": "重置商店刷新时间。" } } } From a26afe7d47a1eaae75d8d1e23145a7f361498cc4 Mon Sep 17 00:00:00 2001 From: Secretboy-SMR Date: Tue, 10 May 2022 22:57:52 +0800 Subject: [PATCH 073/312] Fix language switching prompt and save --- .../emu/grasscutter/command/commands/LanguageCommand.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java b/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java index e16455697..5966c6167 100644 --- a/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java @@ -35,8 +35,9 @@ public final class LanguageCommand implements CommandHandler { if (sender != null) { var locale = Locale.forLanguageTag(langCode); actualLangCode = Utils.getLanguageCode(locale); - sender.getAccount().setLocale(locale); - return; + var account = sender.getAccount(); + account.setLocale(locale); + account.save(); } else { var languageInst = Grasscutter.getLanguage(langCode); From 5d49323c37935401e8ce021026afbce6d860d5b2 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Tue, 10 May 2022 20:00:47 -0400 Subject: [PATCH 074/312] my poor, poor, language system (Formatting refactor) --- .../java/emu/grasscutter/utils/Language.java | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/main/java/emu/grasscutter/utils/Language.java b/src/main/java/emu/grasscutter/utils/Language.java index 57f211020..7c3426384 100644 --- a/src/main/java/emu/grasscutter/utils/Language.java +++ b/src/main/java/emu/grasscutter/utils/Language.java @@ -3,7 +3,6 @@ package emu.grasscutter.utils; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import emu.grasscutter.Grasscutter; -import emu.grasscutter.game.Account; import emu.grasscutter.game.player.Player; import javax.annotation.Nullable; @@ -12,10 +11,11 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.Map; public final class Language { + private static final Map cachedLanguages = new ConcurrentHashMap<>(); + private final JsonObject languageData; private final String languageCode; private final Map cachedTranslations = new ConcurrentHashMap<>(); - private static final Map cachedLanguages = new ConcurrentHashMap<>(); /** * Creates a language instance from a code. @@ -28,16 +28,14 @@ public final class Language { } var fallbackLanguageCode = Utils.getLanguageCode(Grasscutter.getConfig().DefaultLanguage); - var descripter = getLanguageFileStreamDescripter(langCode, fallbackLanguageCode); - var actualLanguageCode = descripter.getLanguageCode(); + var description = getLanguageFileStreamDescripter(langCode, fallbackLanguageCode); + var actualLanguageCode = description.getLanguageCode(); - Language languageInst = null; - - if (descripter.getLanguageFile() != null) { - languageInst = new Language(descripter); + Language languageInst; + if (description.getLanguageFile() != null) { + languageInst = new Language(description); cachedLanguages.put(actualLanguageCode, languageInst); - } - else { + } else { languageInst = cachedLanguages.get(actualLanguageCode); cachedLanguages.put(langCode, languageInst); } @@ -95,39 +93,41 @@ public final class Language { /** * Reads a file and creates a language instance. */ - private Language(InternalLanguageFileStreamDescripter descripter) { + private Language(LanguageStreamDescription description) { @Nullable JsonObject languageData = null; - languageCode = descripter.getLanguageCode(); + languageCode = description.getLanguageCode(); try { - languageData = Grasscutter.getGsonFactory().fromJson(Utils.readFromInputStream(descripter.getLanguageFile()), JsonObject.class); + languageData = Grasscutter.getGsonFactory().fromJson(Utils.readFromInputStream(description.getLanguageFile()), JsonObject.class); } catch (Exception exception) { - Grasscutter.getLogger().warn("Failed to load language file: " + descripter.getLanguageCode(), exception); + Grasscutter.getLogger().warn("Failed to load language file: " + description.getLanguageCode(), exception); } this.languageData = languageData; } /** - * create a InternalLanguageFileStreamDescripter + * create a LanguageStreamDescription * @param languageCode The name of the language code. * @param fallbackLanguageCode The name of the fallback language code. */ - private static InternalLanguageFileStreamDescripter getLanguageFileStreamDescripter(String languageCode, String fallbackLanguageCode) { + private static LanguageStreamDescription getLanguageFileStreamDescripter(String languageCode, String fallbackLanguageCode) { var fileName = languageCode + ".json"; var fallback = fallbackLanguageCode + ".json"; String actualLanguageCode = languageCode; if (cachedLanguages.containsKey(actualLanguageCode)) { - return new InternalLanguageFileStreamDescripter(actualLanguageCode, null); + return new LanguageStreamDescription(actualLanguageCode, null); } + InputStream file = Grasscutter.class.getResourceAsStream("/languages/" + fileName); if (file == null) { // Provided fallback language. actualLanguageCode = fallbackLanguageCode; if (cachedLanguages.containsKey(actualLanguageCode)) { - return new InternalLanguageFileStreamDescripter(actualLanguageCode, null); + return new LanguageStreamDescription(actualLanguageCode, null); } + file = Grasscutter.class.getResourceAsStream("/languages/" + fallback); Grasscutter.getLogger().warn("Failed to load language file: " + fileName + ", falling back to: " + fallback); } @@ -135,8 +135,9 @@ public final class Language { if(file == null) { // Fallback the fallback language. actualLanguageCode = "en-US"; if (cachedLanguages.containsKey(actualLanguageCode)) { - return new InternalLanguageFileStreamDescripter(actualLanguageCode, null); + return new LanguageStreamDescription(actualLanguageCode, null); } + file = Grasscutter.class.getResourceAsStream("/languages/en-US.json"); Grasscutter.getLogger().warn("Failed to load language file: " + fallback + ", falling back to: en-US.json"); } @@ -144,7 +145,7 @@ public final class Language { if(file == null) throw new RuntimeException("Unable to load the primary, fallback, and 'en-US' language files."); - return new InternalLanguageFileStreamDescripter(actualLanguageCode, file); + return new LanguageStreamDescription(actualLanguageCode, file); } /** @@ -180,11 +181,11 @@ public final class Language { this.cachedTranslations.put(key, result); return result; } - private static class InternalLanguageFileStreamDescripter { - private String languageCode; - private InputStream languageFile; + private static class LanguageStreamDescription { + private final String languageCode; + private final InputStream languageFile; - public InternalLanguageFileStreamDescripter(String languageCode, InputStream languageFile) { + public LanguageStreamDescription(String languageCode, InputStream languageFile) { this.languageCode = languageCode; this.languageFile = languageFile; } From 54cf45a72e8a17df2b644fff08531fd56eb917e7 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Tue, 10 May 2022 15:18:01 -0700 Subject: [PATCH 075/312] Claymore charged attack stamina cost --- .../StaminaManager/StaminaManager.java | 249 +++++++++++++----- 1 file changed, 188 insertions(+), 61 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index 8737d9755..0795a152d 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -1,5 +1,6 @@ package emu.grasscutter.game.managers.StaminaManager; +import ch.qos.logback.classic.Logger; import emu.grasscutter.Grasscutter; import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.GameEntity; @@ -55,7 +56,7 @@ public class StaminaManager { MotionState.MOTION_LADDER_TO_STANDBY, // NOT OBSERVED MotionState.MOTION_STANDBY_MOVE, // sustained, recover MotionState.MOTION_STANDBY // sustained, recover - ))); + ))); put("SWIM", new HashSet<>(List.of( MotionState.MOTION_SWIM_IDLE, // sustained MotionState.MOTION_SWIM_DASH, // immediate and sustained @@ -104,6 +105,7 @@ public class StaminaManager { ))); }}; + private final Logger logger = Grasscutter.getLogger(); public final static int GlobalMaximumStamina = 24000; private Position currentCoordinates = new Position(0, 0, 0); private Position previousCoordinates = new Position(0, 0, 0); @@ -118,6 +120,73 @@ public class StaminaManager { private int lastSkillId = 0; private int lastSkillCasterId = 0; private boolean lastSkillFirstTick = true; + public static final HashSet TalentMovements = new HashSet<>(List.of( + 10013, // Kamisato Ayaka + 10413 // Mona + )); + + // TODO: Get from somewhere else, instead of hard-coded here? + public static final HashSet ClaymoreSkills = new HashSet<>(List.of( + 10160, // Diluc, /=2 + 10201, // Razor + 10241, // Beidou + 10341, // Noelle + 10401, // Chongyun + 10441, // Xinyan + 10511, // Eula + 10531, // Sayu + 10571 // Arataki Itto, = 0 + )); + public static final HashSet CatalystSkills = new HashSet<>(List.of( + 10060, // Lisa + 10070, // Barbara + 10271, // Ningguang + 10291, // Klee + 10411, // Mona + 10431, // Sucrose + 10481, // Yanfei + 10541, // Sangonomoiya Kokomi + 10581 // Yae Miko + )); + public static final HashSet PolearmSkills = new HashSet<>(List.of( + 10231, // Xiangling + 10261, // Xiao + 10301, // Zhongli + 10451, // Rosaria + 10461, // Hu Tao + 10501, // Thoma + 10521, // Raiden Shogun + 10631, // Shenhe + 10641 // Yunjin + )); + public static final HashSet SwordSkills = new HashSet<>(List.of( + 10024, // Kamisato Ayaka + 10031, // Jean + 10073, // Kaeya + 10321, // Bennett + 10337, // Tartaglia, melee stance (10332 switch to melee, 10336 switch to ranged stance) + 10351, // Qiqi + 10381, // Xingqiu + 10386, // Albedo + 10421, // Keqing, =-2500 + 10471, // Kaedehara Kazuha + 10661, // Kamisato Ayato + 100553, // Lumine + 100540 // Aether + )); + public static final HashSet BowSkills = new HashSet<>(List.of( + 10041, 10043, // Amber + 10221, 10223,// Venti + 10311, 10315, // Fischl + 10331, 10335, // Tartaglia, ranged stance + 10371, // Ganyu + 10391, 10394, // Diona + 10491, // Yoimiya + 10551, 10554, // Gorou + 10561, 10564, // Kojou Sara + 10621, // Aloy + 99998, 99999 // Yelan // TODO: get real values + )); public StaminaManager(Player player) { @@ -168,7 +237,7 @@ public class StaminaManager { float diffX = currentCoordinates.getX() - previousCoordinates.getX(); float diffY = currentCoordinates.getY() - previousCoordinates.getY(); float diffZ = currentCoordinates.getZ() - previousCoordinates.getZ(); - Grasscutter.getLogger().trace("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates + + logger.trace("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates + ", " + diffX + ", " + diffY + ", " + diffZ); return Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.2 || Math.abs(diffZ) > 0.3; } @@ -182,14 +251,14 @@ public class StaminaManager { for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.type.toString(), consumption); if ((overriddenConsumption.type != consumption.type) && (overriddenConsumption.amount != consumption.amount)) { - Grasscutter.getLogger().debug("[StaminaManager] Stamina update relative(" + + logger.debug("[StaminaManager] Stamina update relative(" + consumption.type.toString() + ", " + consumption.amount + ") overridden to relative(" + consumption.type.toString() + ", " + consumption.amount + ") by: " + listener.getKey()); return currentStamina; } } int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); - Grasscutter.getLogger().trace(currentStamina + "/" + playerMaxStamina + "\t" + currentState + "\t" + + logger.trace(currentStamina + "/" + playerMaxStamina + "\t" + currentState + "\t" + (isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.type + "," + consumption.amount + ")"); int newStamina = currentStamina + consumption.amount; @@ -207,7 +276,7 @@ public class StaminaManager { for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { int overriddenNewStamina = listener.getValue().onBeforeUpdateStamina(reason, newStamina); if (overriddenNewStamina != newStamina) { - Grasscutter.getLogger().debug("[StaminaManager] Stamina update absolute(" + + logger.debug("[StaminaManager] Stamina update absolute(" + reason + ", " + newStamina + ") overridden to absolute(" + reason + ", " + newStamina + ") by: " + listener.getKey()); return currentStamina; @@ -254,7 +323,7 @@ public class StaminaManager { if (!player.isPaused() && sustainedStaminaHandlerTimer == null) { sustainedStaminaHandlerTimer = new Timer(); sustainedStaminaHandlerTimer.scheduleAtFixedRate(new SustainedStaminaHandler(), 0, 200); - Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer started"); + logger.debug("[MovementManager] SustainedStaminaHandlerTimer started"); } } @@ -262,7 +331,7 @@ public class StaminaManager { if (sustainedStaminaHandlerTimer != null) { sustainedStaminaHandlerTimer.cancel(); sustainedStaminaHandlerTimer = null; - Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer stopped"); + logger.debug("[MovementManager] SustainedStaminaHandlerTimer stopped"); } } @@ -276,12 +345,26 @@ public class StaminaManager { return; } setSkillCast(skillId, casterId); + // Handle immediate stamina cost + if (ClaymoreSkills.contains(skillId)) { + // Exclude claymore as their stamina cost starts when MixinStaminaCost gets in + return; + } + // TODO: Differentiate normal attacks from charged attacks and exclude + // TODO: Temporary: Exclude non-claymore attacks for now + if (BowSkills.contains(skillId) + || SwordSkills.contains(skillId) + || PolearmSkills.contains(skillId) + || CatalystSkills.contains(skillId) + ) { + return; + } handleImmediateStamina(session, skillId); } public void handleMixinCostStamina(boolean isSwim) { // Talent moving and claymore avatar charged attack duration - // Grasscutter.getLogger().trace("abilityMixinCostStamina: isSwim: " + isSwim); + // logger.trace("abilityMixinCostStamina: isSwim: " + isSwim); if (lastSkillCasterId == player.getTeamManager().getCurrentAvatarEntity().getId()) { handleImmediateStamina(cachedSession, lastSkillId); } @@ -299,7 +382,7 @@ public class StaminaManager { return; } currentState = motionState; - // Grasscutter.getLogger().trace("" + currentState); + // logger.trace("" + currentState); Vector posVector = motionInfo.getPos(); Position newPos = new Position(posVector.getX(), posVector.getY(), posVector.getZ()); if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) { @@ -337,8 +420,6 @@ public class StaminaManager { } private void handleImmediateStamina(GameSession session, int skillId) { - // Non-claymore avatar attacks - // TODO: differentiate charged vs normal attack Consumption consumption = getFightConsumption(skillId); updateStaminaRelative(session, consumption); } @@ -349,7 +430,7 @@ public class StaminaManager { int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); int maxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); if (moving || (currentStamina < maxStamina)) { - Grasscutter.getLogger().trace("Player moving: " + moving + ", stamina full: " + + logger.trace("Player moving: " + moving + ", stamina full: " + (currentStamina >= maxStamina) + ", recalculate stamina"); Consumption consumption; @@ -396,7 +477,7 @@ public class StaminaManager { // For others recover after 2 seconds (10 ticks) - as official server does. staminaRecoverDelay++; consumption.amount = 0; - Grasscutter.getLogger().trace("[StaminaManager] Delaying recovery: " + staminaRecoverDelay); + logger.trace("[StaminaManager] Delaying recovery: " + staminaRecoverDelay); } } updateStaminaRelative(cachedSession, consumption); @@ -414,7 +495,7 @@ public class StaminaManager { private void handleDrowning() { int stamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); if (stamina < 10) { - Grasscutter.getLogger().trace(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + + logger.trace(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState); if (currentState != MotionState.MOTION_SWIM_IDLE) { killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_DRAWN); @@ -427,52 +508,32 @@ public class StaminaManager { // Stamina Consumption Reduction: https://genshin-impact.fandom.com/wiki/Stamina private Consumption getFightConsumption(int skillCasting) { - /* TODO: - Instead of handling here, consider call StaminaManager.updateStamina****() with a Consumption object with - type=FIGHT and a modified amount when handling attacks for more accurate attack start/end time and - other info. Handling it here could be very complicated. - Charged attack - Default: - Polearm: (-2500) - Claymore: (-4000 per second, -800 each tick) - Catalyst: (-5000) - Talent: - Ningguang: When Ningguang is in possession of Star Jades, her Charged Attack does not consume Stamina. (Catalyst * 0) - Klee: When Jumpy Dumpty and Normal Attacks deal DMG, Klee has a 50% chance to obtain an Explosive Spark. - This Explosive Spark is consumed by the next Charged Attack, which costs no Stamina. (Catalyst * 0) - Constellations: - Hu Tao: While in a Paramita Papilio state activated by Guide to Afterlife, Hu Tao's Charge Attacks do not consume Stamina. (Polearm * 0) - Character Specific: - Keqing: (-2500) - Diluc: (Claymore * 0.5) - Talent Moving: (Those are skills too) - Ayaka: (-1000 initial) (-1500 per second) When the Cryo application at the end of Kamisato Art: Senho hits an opponent (+1000) - Mona: (-1000 initial) (-1500 per second) - */ - - // TODO: Currently only handling Ayaka and Mona's talent moving initial costs. - Consumption consumption = new Consumption(); - // Talent moving - HashMap> talentMovementConsumptions = new HashMap<>() {{ - // List[0] = initial cost, [1] = sustained cost. Sustained costs are divided by 3 per second as MixinStaminaCost is triggered at 3Hz. - put(10013, List.of(new Consumption(ConsumptionType.TALENT_DASH_START, -1000), new Consumption(ConsumptionType.TALENT_DASH, -500))); // Kamisato Ayaka - put(10413, List.of(new Consumption(ConsumptionType.TALENT_DASH_START, -1000), new Consumption(ConsumptionType.TALENT_DASH, -500))); // Mona - }}; - if (talentMovementConsumptions.containsKey(skillCasting)) { - if (lastSkillFirstTick) { - consumption = talentMovementConsumptions.get(skillCasting).get(0); - } else { - lastSkillFirstTick = false; - consumption = talentMovementConsumptions.get(skillCasting).get(1); - } + if (TalentMovements.contains(skillCasting)) { + // TODO: recover 1000 if kamisato hits an enemy at the end of dashing + return getTalentMovingSustainedCost(skillCasting); } - // TODO: Claymore avatar charged attack - // HashMap fightConsumptions = new HashMap<>(); - - // TODO: Non-claymore avatar charged attack - - return consumption; + // Bow avatar charged attack + if (BowSkills.contains(skillCasting)) { + return getBowSustainedCost(skillCasting); + } + // Claymore avatar charged attack + if (ClaymoreSkills.contains(skillCasting)) { + return getClaymoreSustainedCost(skillCasting); + } + // Catalyst avatar charged attack + if (CatalystSkills.contains(skillCasting)) { + return getCatalystSustainedCost(skillCasting); + } + // Polearm avatar charged attack + if (PolearmSkills.contains(skillCasting)) { + return getPolearmSustainedCost(skillCasting); + } + // Sword avatar charged attack + if (SwordSkills.contains(skillCasting)) { + return getSwordSustainedCost(skillCasting); + } + return new Consumption(); } private Consumption getClimbConsumption() { @@ -550,13 +611,17 @@ public class StaminaManager { if (currentState == MotionState.MOTION_SKIFF_POWERED_DASH) { return new Consumption(ConsumptionType.POWERED_SKIFF); } - Consumption consumption = new Consumption(ConsumptionType.SKIFF); // No known reduction for skiffing. - return consumption; + return new Consumption(ConsumptionType.SKIFF); } private Consumption getOtherConsumptions() { - // TODO: Add logic + if (currentState == MotionState.MOTION_NOTIFY) { + if (BowSkills.contains(lastSkillId)) { + return new Consumption(ConsumptionType.FIGHT, 500); + } + } + // TODO: Add other logic return new Consumption(); } @@ -584,4 +649,66 @@ public class StaminaManager { float reduction = 1; return reduction; } + + private Consumption getTalentMovingSustainedCost(int skillId) { + if (lastSkillFirstTick) { + lastSkillFirstTick = false; + return new Consumption(ConsumptionType.TALENT_DASH, -1000); + } else { + return new Consumption(ConsumptionType.TALENT_DASH, -500); + } + } + + private Consumption getBowSustainedCost(int skillId) { + // Note that bow skills actually recovers stamina + // Character specific handling + // switch (skillId) { + // // No known bow skills cost stamina + // } + return new Consumption(ConsumptionType.FIGHT, +500); + } + + private Consumption getCatalystSustainedCost(int skillId) { + Consumption consumption = new Consumption(ConsumptionType.FIGHT, -5000); + // Character specific handling + switch (skillId) { + // TODO: Yanfei + } + return consumption; + } + + private Consumption getClaymoreSustainedCost(int skillId) { + Consumption consumption = new Consumption(ConsumptionType.FIGHT, -1333); // 4000 / 3 = 1333 + // Character specific handling + switch (skillId) { + case 10571: // Arataki Itto, does not consume stamina at all. + consumption.amount = 0; + break; + case 10160: // Diluc, with talent "Relentless" stamina cost is decreased by 50% + // TODO: How to get talent status? + consumption.amount /= 2; + break; + } + return consumption; + } + + private Consumption getPolearmSustainedCost(int skillId) { + Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2500); + // Character specific handling + switch (skillId) { + // TODO: + } + return consumption; + } + + private Consumption getSwordSustainedCost(int skillId) { + Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2000); + // Character specific handling + switch (skillId) { + case 10421: // Keqing, -2500 + consumption.amount = -2500; + break; + } + return consumption; + } } From c274907e9a2962ac798a9c7f9cf8c8fc50f12d39 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Tue, 10 May 2022 22:49:25 -0400 Subject: [PATCH 076/312] Create new config class --- .../java/emu/grasscutter/Configuration.java | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 src/main/java/emu/grasscutter/Configuration.java diff --git a/src/main/java/emu/grasscutter/Configuration.java b/src/main/java/emu/grasscutter/Configuration.java new file mode 100644 index 000000000..9437b1655 --- /dev/null +++ b/src/main/java/emu/grasscutter/Configuration.java @@ -0,0 +1,166 @@ +package emu.grasscutter; + +import emu.grasscutter.Grasscutter.*; +import emu.grasscutter.game.mail.Mail.*; + +import java.util.Locale; + +/** + * A data container for the server's configuration. + */ +public final class Configuration { + public Structure folderStructure; + public Database databaseInfo; + public Language language; + public Server server; + + /* Option containers. */ + + public static class Database { + public String connectionUri = "mongodb://localhost:27017"; + public String collection = "grasscutter"; + } + + public static class Structure { + public String resources = "./resources/"; + public String data = "./data/"; + public String packets = "./packets/"; + public String keys = "./keys/"; + public String scripts = "./resources/scripts/"; + public String plugins = "./plugins/"; + + // UNUSED (potentially added later?) + // public String dumps = "./dumps/"; + } + + public static class Server { + public ServerDebugMode debugLevel = ServerDebugMode.NONE; + public ServerRunMode runMode = ServerRunMode.HYBRID; + + public Dispatch dispatch = new Dispatch(); + public Game game = new Game(); + } + + public static class Language { + public Locale language = Locale.getDefault(); + public Locale fallback = Locale.US; + } + + /* Server options. */ + + public static class Dispatch { + public String bindAddress = "0.0.0.0"; + /* This is the address used in URLs. */ + public String accessAddress = "127.0.0.1"; + + public int bindPort = 443; + /* This is the port used in URLs. */ + public int accessPort = 443; + + public Encryption encryption = new Encryption(); + public Policies policies = new Policies(); + public Region[] regions = {}; + } + + public static class Game { + public String bindAddress = "0.0.0.0"; + /* This is the address used in the default region. */ + public String accessAddress = "127.0.0.1"; + + public int bindPort = 443; + /* This is the port used in the default region. */ + public int accessPort = 443; + + public GameOptions gameOptions = new GameOptions(); + public JoinOptions joinOptions = new JoinOptions(); + public ConsoleAccount serverAccount = new ConsoleAccount(); + } + + /* Data containers. */ + + public static class Encryption { + public boolean useEncryption = true; + /* Should 'https' be appended to URLs? */ + public boolean useInRouting = true; + public String keystore = "./keystore.p12"; + public String keystorePassword = "123456"; + } + + public static class Policies { + public CORS cors = new CORS(); + + public static class CORS { + public boolean enabled = false; + public String[] allowedOrigins = new String[]{"*"}; + } + } + + public static class GameOptions { + public InventoryLimits inventoryLimits = new InventoryLimits(); + public AvatarLimits avatarLimits = new AvatarLimits(); + public int worldEntityLimit = 1000; // Unenforced. TODO: Implement. + + public boolean watchGachaConfiguration = false; + public boolean enableShopItems = true; + public Rates rates = new Rates(); + + public static class InventoryLimits { + public int weapons = 2000; + public int relics = 2000; + public int materials = 2000; + public int furniture = 2000; + public int all = 30000; + } + + public static class AvatarLimits { + public int singlePlayerTeam = 4; + public int multiplayerTeam = 4; + } + + public static class Rates { + public float adventureExp = 1.0f; + public float mora = 1.0f; + public float leyLines = 1.0f; + } + } + + public static class JoinOptions { + public int[] welcomeEmotes = {2007, 1002, 4010}; + public String welcomeMessage = "Welcome to a Grasscutter server."; + + public static class Mail { + public String title = "Welcome to Grasscutter!"; + public String content = """ + Hi there!\r + First of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r + \r + Check out our:\r + + """; + public String sender = "Lawnmower"; + public MailItem[] items = { + new MailItem(13509, 1, 1), + new MailItem(201, 99999, 1) + }; + } + } + + public static class ConsoleAccount { + public int avatarId = 10000007; + public int nameCardId = 210001; + public int adventureRank = 1; + public int worldLevel = 0; + + public String nickName = "Server"; + public String signature = "Welcome to Grasscutter!"; + } + + /* Objects. */ + + public static class Region { + public String Name = "os_usa"; + public String Title = "Grasscutter"; + public String Ip = "127.0.0.1"; + public int Port = 22102; + } +} \ No newline at end of file From 11161227ab8713ce8b08f4cdef112313f9fb173b Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 11 May 2022 00:30:07 -0400 Subject: [PATCH 077/312] Convert to the far superior config system --- .../java/emu/grasscutter/Configuration.java | 137 +++++++++++++++++- .../java/emu/grasscutter/Grasscutter.java | 99 ++++++++----- .../command/commands/ReloadCommand.java | 2 +- .../emu/grasscutter/data/ResourceLoader.java | 30 ++-- .../grasscutter/database/DatabaseManager.java | 19 +-- .../java/emu/grasscutter/game/Account.java | 6 +- .../grasscutter/game/drop/DropManager.java | 8 +- .../game/expedition/ExpeditionManager.java | 4 +- .../grasscutter/game/gacha/GachaBanner.java | 15 +- .../grasscutter/game/gacha/GachaManager.java | 34 ++--- .../grasscutter/game/inventory/Inventory.java | 42 +++--- .../StaminaManager/StaminaManager.java | 5 +- .../emu/grasscutter/game/player/Player.java | 8 +- .../emu/grasscutter/game/player/TeamInfo.java | 10 +- .../grasscutter/game/player/TeamManager.java | 25 ++-- .../grasscutter/game/shop/ShopManager.java | 10 +- .../game/tower/TowerScheduleManager.java | 19 ++- .../emu/grasscutter/game/world/World.java | 15 -- .../java/emu/grasscutter/plugin/Plugin.java | 4 +- .../emu/grasscutter/plugin/PluginManager.java | 5 +- .../scripts/SceneScriptManager.java | 12 +- .../server/dispatch/AnnouncementHandler.java | 21 +-- .../dispatch/DispatchHttpJsonHandler.java | 5 +- .../server/dispatch/DispatchServer.java | 110 ++++++-------- .../DefaultAuthenticationHandler.java | 5 +- .../dispatch/http/GachaRecordHandler.java | 9 +- .../grasscutter/server/game/GameServer.java | 8 +- .../server/game/GameServerPacketHandler.java | 4 +- .../grasscutter/server/game/GameSession.java | 10 +- .../recv/HandlerSetPlayerBornDataReq.java | 11 +- .../send/PacketGetPlayerFriendListRsp.java | 18 ++- .../packet/send/PacketPlayerLoginRsp.java | 14 +- .../packet/send/PacketPlayerStoreNotify.java | 6 +- .../packet/send/PacketPullRecentChatRsp.java | 19 ++- .../send/PacketStoreWeightLimitNotify.java | 13 +- .../java/emu/grasscutter/tools/Tools.java | 88 +++++------ .../java/emu/grasscutter/utils/Crypto.java | 12 +- .../java/emu/grasscutter/utils/Language.java | 8 +- .../java/emu/grasscutter/utils/Utils.java | 7 +- 39 files changed, 504 insertions(+), 373 deletions(-) diff --git a/src/main/java/emu/grasscutter/Configuration.java b/src/main/java/emu/grasscutter/Configuration.java index 9437b1655..369160f42 100644 --- a/src/main/java/emu/grasscutter/Configuration.java +++ b/src/main/java/emu/grasscutter/Configuration.java @@ -3,17 +3,141 @@ package emu.grasscutter; import emu.grasscutter.Grasscutter.*; import emu.grasscutter.game.mail.Mail.*; +import java.lang.reflect.Field; +import java.util.Arrays; import java.util.Locale; +import static emu.grasscutter.Grasscutter.config; + /** * A data container for the server's configuration. + * + * Use `import static emu.grasscutter.Configuration.*;` + * to import all configuration constants. */ public final class Configuration { + private static int version() { + return 1; + } + + /** + * Attempts to update the server's existing configuration to the latest configuration. + */ + public static void updateConfig() { + var existing = config.version; + var latest = version(); + + if(existing == latest) + return; + + // Create a new configuration instance. + Configuration updated = new Configuration(); + // Update all configuration fields. + Field[] fields = Configuration.class.getDeclaredFields(); + Arrays.stream(fields).forEach(field -> { + try { + field.set(updated, field.get(config)); + } catch (Exception exception) { + Grasscutter.getLogger().error("Failed to update a configuration field.", exception); + } + }); + + try { // Save configuration & reload. + Grasscutter.saveConfig(updated); + Grasscutter.reloadConfig(); + } catch (Exception exception) { + Grasscutter.getLogger().warn("Failed to inject the updated configuration.", exception); + } + } + + /* + * Constants + */ + + // 'c' is short for 'config' and makes code look 'cleaner'. + public static final Configuration c = config; + + public static final Locale LANGUAGE = config.language.language; + public static final Locale FALLBACK_LANGUAGE = config.language.fallback; + public static final String DATA_FOLDER = config.folderStructure.data; + public static final String RESOURCES_FOLDER = config.folderStructure.resources; + public static final String KEYS_FOLDER = config.folderStructure.keys; + public static final String PLUGINS_FOLDER = config.folderStructure.plugins; + public static final String SCRIPTS_FOLDER = config.folderStructure.scripts; + public static final String PACKETS_FOLDER = config.folderStructure.packets; + + public static final Server SERVER = config.server; + public static final Database DATABASE = config.databaseInfo; + public static final Account ACCOUNT = config.account; + + public static final Dispatch DISPATCH_INFO = config.server.dispatch; + public static final Game GAME_INFO = config.server.game; + + public static final Encryption DISPATCH_ENCRYPTION = config.server.dispatch.encryption; + public static final Policies DISPATCH_POLICIES = config.server.dispatch.policies; + + public static final GameOptions GAME_OPTIONS = config.server.game.gameOptions; + public static final GameOptions.InventoryLimits INVENTORY_LIMITS = config.server.game.gameOptions.inventoryLimits; + + /* + * Utilities + */ + + public static String DATA(String path) { + return DATA_FOLDER + "/" + path; + } + + public static String RESOURCE(String path) { + return RESOURCES_FOLDER + "/" + path; + } + + public static String SCRIPT(String path) { + return SCRIPTS_FOLDER + "/" + path; + } + + /** + * Fallback method. + * @param left Attempt to use. + * @param right Use if left is undefined. + * @return Left or right. + */ + public static T lr(T left, T right) { + return left == null ? right : left; + } + + /** + * {@link Configuration#lr(Object, Object)} for {@link String}s. + * @param left Attempt to use. + * @param right Use if left is empty. + * @return Left or right. + */ + public static String lr(String left, String right) { + return left.isEmpty() ? right : left; + } + + /** + * {@link Configuration#lr(Object, Object)} for {@link Integer}s. + * @param left Attempt to use. + * @param right Use if left is 0. + * @return Left or right. + */ + public static int lr(int left, int right) { + return left == 0 ? right : left; + } + + /* + * Configuration data. + */ + public Structure folderStructure; public Database databaseInfo; public Language language; + public Account account; public Server server; + // DO NOT. TOUCH. THE VERSION NUMBER. + public int version = version(); + /* Option containers. */ public static class Database { @@ -45,6 +169,11 @@ public final class Configuration { public Locale language = Locale.getDefault(); public Locale fallback = Locale.US; } + + public static class Account { + public boolean autoCreate = false; + public String[] defaultPermissions = {}; + } /* Server options. */ @@ -60,6 +189,8 @@ public final class Configuration { public Encryption encryption = new Encryption(); public Policies policies = new Policies(); public Region[] regions = {}; + + public String defaultName = "Grasscutter"; } public static class Game { @@ -100,10 +231,13 @@ public final class Configuration { public AvatarLimits avatarLimits = new AvatarLimits(); public int worldEntityLimit = 1000; // Unenforced. TODO: Implement. - public boolean watchGachaConfiguration = false; + public boolean watchGachaConfig = false; public boolean enableShopItems = true; + public boolean staminaUsage = true; public Rates rates = new Rates(); + public Database databaseInfo = new Database(); + public static class InventoryLimits { public int weapons = 2000; public int relics = 2000; @@ -127,6 +261,7 @@ public final class Configuration { public static class JoinOptions { public int[] welcomeEmotes = {2007, 1002, 4010}; public String welcomeMessage = "Welcome to a Grasscutter server."; + public Mail welcomeMail = new Mail(); public static class Mail { public String title = "Welcome to Grasscutter!"; diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 8bdb3c207..07cc1f240 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -1,9 +1,8 @@ package emu.grasscutter; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOError; +import java.io.*; +import java.lang.reflect.Field; +import java.util.Arrays; import java.util.Calendar; import emu.grasscutter.command.CommandMap; @@ -32,13 +31,15 @@ import emu.grasscutter.server.game.GameServer; import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.Crypto; +import javax.annotation.Nullable; + import static emu.grasscutter.utils.Language.translate; +import static emu.grasscutter.Configuration.*; public final class Grasscutter { private static final Logger log = (Logger) LoggerFactory.getLogger(Grasscutter.class); private static LineReader consoleLineReader = null; - - private static Config config; + private static Language language; private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); @@ -51,13 +52,14 @@ public final class Grasscutter { private static PluginManager pluginManager; public static final Reflections reflector = new Reflections("emu.grasscutter"); + public static final Configuration config; static { // Declare logback configuration. System.setProperty("logback.configurationFile", "src/main/resources/logback.xml"); // Load server configuration. - Grasscutter.loadConfig(); + config = Grasscutter.loadConfig(); // Load translation files. Grasscutter.loadLanguage(); @@ -66,9 +68,9 @@ public final class Grasscutter { Utils.startupCheck(); } - public static void main(String[] args) throws Exception { - Crypto.loadKeys(); // Load keys from buffers. - + public static void main(String[] args) throws Exception { + Crypto.loadKeys(); // Load keys from buffers. + // Parse arguments. boolean exitEarly = false; for (String arg : args) { @@ -77,25 +79,25 @@ public final class Grasscutter { Tools.createGmHandbook(); exitEarly = true; } case "-gachamap" -> { - Tools.createGachaMapping(Grasscutter.getConfig().DATA_FOLDER + "/gacha_mappings.js"); exitEarly = true; + Tools.createGachaMapping(DATA("gacha_mappings.js")); exitEarly = true; } } } // Exit early if argument sets it. if(exitEarly) System.exit(0); - + // Initialize server. Grasscutter.getLogger().info(translate("messages.status.starting")); - + // Load all resources. Grasscutter.updateDayOfWeek(); ResourceLoader.loadAll(); ScriptLoader.init(); - + // Initialize database. DatabaseManager.initialize(); - + // Create server instances. dispatchServer = new DispatchServer(); gameServer = new GameServer(); @@ -103,31 +105,32 @@ public final class Grasscutter { new ServerHook(gameServer, dispatchServer); // Create plugin manager instance. pluginManager = new PluginManager(); - + // Start servers. - if (getConfig().RunMode == ServerRunMode.HYBRID) { + var runMode = SERVER.runMode; + if (runMode == ServerRunMode.HYBRID) { dispatchServer.start(); gameServer.start(); - } else if (getConfig().RunMode == ServerRunMode.DISPATCH_ONLY) { + } else if (runMode == ServerRunMode.DISPATCH_ONLY) { dispatchServer.start(); - } else if (getConfig().RunMode == ServerRunMode.GAME_ONLY) { + } else if (runMode == ServerRunMode.GAME_ONLY) { gameServer.start(); } else { - getLogger().error(translate("messages.status.run_mode_error", getConfig().RunMode)); + getLogger().error(translate("messages.status.run_mode_error", runMode)); getLogger().error(translate("messages.status.run_mode_help")); getLogger().error(translate("messages.status.shutdown")); System.exit(1); } - + // Enable all plugins. pluginManager.enablePlugins(); - + // Hook into shutdown event. Runtime.getRuntime().addShutdownHook(new Thread(Grasscutter::onShutdown)); - + // Open console. startConsole(); - } + } /** * Server shutdown event. @@ -137,32 +140,60 @@ public final class Grasscutter { pluginManager.disablePlugins(); } - public static void loadConfig() { + /** + * Attempts to load the configuration from a file. + * @return The config from the file, or a new instance. + */ + public static Configuration loadConfig() { try (FileReader file = new FileReader(configFile)) { - config = gson.fromJson(file, Config.class); - saveConfig(); + return gson.fromJson(file, Configuration.class); } catch (Exception e) { - Grasscutter.config = new Config(); - saveConfig(); + Grasscutter.saveConfig(null); + return new Configuration(); } } + /** + * Attempts to reload the configuration from the file. + * Uses reflection to **replace** the fields in the config. + */ + public static void reloadConfig() { + Configuration fileConfig = Grasscutter.loadConfig(); + + Field[] fields = Configuration.class.getDeclaredFields(); + Arrays.stream(fields).forEach(field -> { + try { + field.set(config, field.get(fileConfig)); + } catch (Exception exception) { + Grasscutter.getLogger().error("Failed to update a configuration field.", exception); + } + }); + } + public static void loadLanguage() { - var locale = config.LocaleLanguage; + var locale = config.language.language; language = Language.getLanguage(Utils.getLanguageCode(locale)); } - public static void saveConfig() { + /** + * Saves the provided server configuration. + * @param config The configuration to save, or null for a new one. + */ + public static void saveConfig(@Nullable Configuration config) { + if(config == null) config = new Configuration(); + try (FileWriter file = new FileWriter(configFile)) { file.write(gson.toJson(config)); + } catch (IOException ignored) { + Grasscutter.getLogger().error("Unable to write to config file."); } catch (Exception e) { - Grasscutter.getLogger().error("Unable to save config file."); + Grasscutter.getLogger().error("Unable to save config file.", e); } } public static void startConsole() { // Console should not start in dispatch only mode. - if (getConfig().RunMode == ServerRunMode.DISPATCH_ONLY) { + if (SERVER.runMode == ServerRunMode.DISPATCH_ONLY) { getLogger().info(translate("messages.dispatch.no_commands_error")); return; } @@ -198,7 +229,7 @@ public final class Grasscutter { } } - public static Config getConfig() { + public static Configuration getConfig() { return config; } diff --git a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java index 9414a89c4..40b8994ec 100644 --- a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java @@ -16,7 +16,7 @@ public final class ReloadCommand implements CommandHandler { public void execute(Player sender, Player targetPlayer, List args) { CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_start")); - Grasscutter.loadConfig(); + Grasscutter.reloadConfig(); Grasscutter.loadLanguage(); Grasscutter.getGameServer().getGachaManager().load(); Grasscutter.getGameServer().getDropManager().load(); diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index c2708bd63..844bbec5e 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -25,10 +25,11 @@ import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierActionType; import emu.grasscutter.data.custom.AbilityModifierEntry; import emu.grasscutter.data.custom.OpenConfigEntry; import emu.grasscutter.data.custom.ScenePointEntry; -import emu.grasscutter.game.world.SpawnDataEntry; -import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry; +import emu.grasscutter.game.world.SpawnDataEntry.*; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import static emu.grasscutter.Configuration.*; + public class ResourceLoader { public static List> getResourceDefClasses() { @@ -127,7 +128,7 @@ public class ResourceLoader { @SuppressWarnings({"rawtypes", "unchecked"}) protected static void loadFromResource(Class c, String fileName, Int2ObjectMap map) throws Exception { - FileReader fileReader = new FileReader(Grasscutter.getConfig().RESOURCE_FOLDER + "ExcelBinOutput/" + fileName); + FileReader fileReader = new FileReader(RESOURCE("ExcelBinOutput/" + fileName)); Gson gson = Grasscutter.getGsonFactory(); List list = gson.fromJson(fileReader, List.class); @@ -141,7 +142,7 @@ public class ResourceLoader { private static void loadScenePoints() { Pattern pattern = Pattern.compile("(?<=scene)(.*?)(?=_point.json)"); - File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Scene/Point"); + File folder = new File(RESOURCE("BinOutput/Scene/Point")); if (!folder.isDirectory() || !folder.exists() || folder.listFiles() == null) { Grasscutter.getLogger().error("Scene point files cannot be found, you cannot use teleport waypoints!"); @@ -150,8 +151,7 @@ public class ResourceLoader { List scenePointList = new ArrayList<>(); for (File file : Objects.requireNonNull(folder.listFiles())) { - ScenePointConfig config = null; - Integer sceneId = null; + ScenePointConfig config; Integer sceneId; Matcher matcher = pattern.matcher(file.getName()); if (matcher.find()) { @@ -190,7 +190,7 @@ public class ResourceLoader { private static void loadAbilityEmbryos() { // Read from cached file if exists - File embryoCache = new File(Grasscutter.getConfig().DATA_FOLDER + "AbilityEmbryos.json"); + File embryoCache = new File(DATA("AbilityEmbryos.json")); List embryoList = null; if (embryoCache.exists()) { @@ -205,7 +205,7 @@ public class ResourceLoader { Pattern pattern = Pattern.compile("(?<=ConfigAvatar_)(.*?)(?=.json)"); embryoList = new LinkedList<>(); - File folder = new File(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Avatar/")); + File folder = new File(Utils.toFilePath(RESOURCE("BinOutput/Avatar/"))); File[] files = folder.listFiles(); if(files == null) { Grasscutter.getLogger().error("Error loading ability embryos: no files found in " + folder.getAbsolutePath()); @@ -252,7 +252,7 @@ public class ResourceLoader { private static void loadAbilityModifiers() { // Load from BinOutput - File folder = new File(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Ability/Temp/AvatarAbilities/")); + File folder = new File(Utils.toFilePath(RESOURCE("BinOutput/Ability/Temp/AvatarAbilities/"))); File[] files = folder.listFiles(); if (files == null) { Grasscutter.getLogger().error("Error loading ability modifiers: no files found in " + folder.getAbsolutePath()); @@ -260,7 +260,7 @@ public class ResourceLoader { } for (File file : files) { - List abilityConfigList = null; + List abilityConfigList; try (FileReader fileReader = new FileReader(file)) { abilityConfigList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, AbilityConfigData.class).getType()); @@ -315,7 +315,7 @@ public class ResourceLoader { private static void loadSpawnData() { // Read from cached file if exists - File spawnDataEntries = new File(Grasscutter.getConfig().DATA_FOLDER + "Spawns.json"); + File spawnDataEntries = new File(DATA("Spawns.json")); List spawnEntryList = null; if (spawnDataEntries.exists()) { @@ -333,16 +333,14 @@ public class ResourceLoader { } for (SpawnGroupEntry entry : spawnEntryList) { - entry.getSpawns().stream().forEach(s -> { - s.setGroup(entry); - }); + entry.getSpawns().forEach(s -> s.setGroup(entry)); GameDepot.getSpawnListById(entry.getSceneId()).insert(entry, entry.getPos().getX(), entry.getPos().getZ()); } } private static void loadOpenConfig() { // Read from cached file if exists - File openConfigCache = new File(Grasscutter.getConfig().DATA_FOLDER + "OpenConfig.json"); + File openConfigCache = new File(DATA("OpenConfig.json")); List list = null; if (openConfigCache.exists()) { @@ -357,7 +355,7 @@ public class ResourceLoader { String[] folderNames = {"BinOutput/Talent/EquipTalents/", "BinOutput/Talent/AvatarTalents/"}; for (String name : folderNames) { - File folder = new File(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + name)); + File folder = new File(Utils.toFilePath(RESOURCE(name))); File[] files = folder.listFiles(); if(files == null) { Grasscutter.getLogger().error("Error loading open config: no files found in " + folder.getAbsolutePath()); return; diff --git a/src/main/java/emu/grasscutter/database/DatabaseManager.java b/src/main/java/emu/grasscutter/database/DatabaseManager.java index 90ff17238..37bda042b 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseManager.java +++ b/src/main/java/emu/grasscutter/database/DatabaseManager.java @@ -1,6 +1,5 @@ package emu.grasscutter.database; -import com.mongodb.MongoClientURI; import com.mongodb.MongoCommandException; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; @@ -21,11 +20,9 @@ import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.player.Player; +import static emu.grasscutter.Configuration.*; + public final class DatabaseManager { - - private static MongoClient mongoClient; - private static MongoClient dispatchMongoClient; - private static Datastore datastore; private static Datastore dispatchDatastore; @@ -44,7 +41,7 @@ public final class DatabaseManager { // Yes. I very dislike this method. However, this will be good for now. // TODO: Add dispatch routes for player account management public static Datastore getAccountDatastore() { - if(Grasscutter.getConfig().RunMode == ServerRunMode.GAME_ONLY) { + if(SERVER.runMode == ServerRunMode.GAME_ONLY) { return dispatchDatastore; } else { return datastore; @@ -53,13 +50,13 @@ public final class DatabaseManager { public static void initialize() { // Initialize - MongoClient mongoClient = MongoClients.create(Grasscutter.getConfig().DatabaseUrl); + MongoClient mongoClient = MongoClients.create(DATABASE.connectionUri); // Set mapper options. MapperOptions mapperOptions = MapperOptions.builder() .storeEmpties(true).storeNulls(false).build(); // Create data store. - datastore = Morphia.createDatastore(mongoClient, Grasscutter.getConfig().DatabaseCollection, mapperOptions); + datastore = Morphia.createDatastore(mongoClient, DATABASE.collection, mapperOptions); // Map classes. datastore.getMapper().map(mappedClasses); @@ -80,9 +77,9 @@ public final class DatabaseManager { } } - if(Grasscutter.getConfig().RunMode == ServerRunMode.GAME_ONLY) { - dispatchMongoClient = MongoClients.create(Grasscutter.getConfig().getGameServerOptions().DispatchServerDatabaseUrl); - dispatchDatastore = Morphia.createDatastore(dispatchMongoClient, Grasscutter.getConfig().getGameServerOptions().DispatchServerDatabaseCollection); + if(SERVER.runMode == ServerRunMode.GAME_ONLY) { + MongoClient dispatchMongoClient = MongoClients.create(GAME_OPTIONS.databaseInfo.connectionUri); + dispatchDatastore = Morphia.createDatastore(dispatchMongoClient, GAME_OPTIONS.databaseInfo.collection); // Ensure indexes for dispatch server try { diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java index 821dea80b..6c3daf61a 100644 --- a/src/main/java/emu/grasscutter/game/Account.java +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -12,7 +12,7 @@ import java.util.Locale; import org.bson.Document; -import com.mongodb.DBObject; +import static emu.grasscutter.Configuration.*; @Entity(value = "accounts", useDiscriminator = false) public class Account { @@ -34,7 +34,7 @@ public class Account { @Deprecated public Account() { this.permissions = new ArrayList<>(); - this.locale = Grasscutter.getConfig().LocaleLanguage; + this.locale = LANGUAGE; } public String getId() { @@ -180,7 +180,7 @@ public class Account { // Set account default language as server default language if (!document.containsKey("locale")) { - this.locale = Grasscutter.getConfig().LocaleLanguage; + this.locale = LANGUAGE; } } } diff --git a/src/main/java/emu/grasscutter/game/drop/DropManager.java b/src/main/java/emu/grasscutter/game/drop/DropManager.java index e304d37b5..218624d1a 100644 --- a/src/main/java/emu/grasscutter/game/drop/DropManager.java +++ b/src/main/java/emu/grasscutter/game/drop/DropManager.java @@ -21,6 +21,8 @@ import java.io.FileReader; import java.util.Collection; import java.util.List; +import static emu.grasscutter.Configuration.*; + public class DropManager { public GameServer getGameServer() { return gameServer; @@ -41,7 +43,7 @@ public class DropManager { } public synchronized void load() { - try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "Drop.json")) { + try (FileReader fileReader = new FileReader(DATA("Drop.json"))) { getDropData().clear(); List banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, DropInfo.class).getType()); if(banners.size() > 0) { @@ -69,9 +71,7 @@ public class DropManager { } else { // target is null if items will be added are shared. no one could pick it up because of the combination(give + shared) // so it will be sent to all players' inventories directly. - dropScene.getPlayers().forEach(x -> { - x.getInventory().addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true); - }); + dropScene.getPlayers().forEach(x -> x.getInventory().addItem(new GameItem(itemData, num), ActionReason.SubfieldDrop, true)); } } } diff --git a/src/main/java/emu/grasscutter/game/expedition/ExpeditionManager.java b/src/main/java/emu/grasscutter/game/expedition/ExpeditionManager.java index 5d1b652e1..1b75d7306 100644 --- a/src/main/java/emu/grasscutter/game/expedition/ExpeditionManager.java +++ b/src/main/java/emu/grasscutter/game/expedition/ExpeditionManager.java @@ -10,6 +10,8 @@ import java.io.FileReader; import java.util.Collection; import java.util.List; +import static emu.grasscutter.Configuration.*; + public class ExpeditionManager { public GameServer getGameServer() { return gameServer; @@ -28,7 +30,7 @@ public class ExpeditionManager { } public synchronized void load() { - try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "ExpeditionReward.json")) { + try (FileReader fileReader = new FileReader(DATA("ExpeditionReward.json"))) { getExpeditionRewardDataList().clear(); List banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ExpeditionRewardInfo.class).getType()); if(banners.size() > 0) { diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java index b48cb0898..dce433fcf 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -1,9 +1,10 @@ package emu.grasscutter.game.gacha; -import emu.grasscutter.Grasscutter; import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo; import emu.grasscutter.net.proto.GachaUpInfoOuterClass.GachaUpInfo; +import static emu.grasscutter.Configuration.*; + public class GachaBanner { private int gachaType; private int scheduleId; @@ -95,15 +96,11 @@ public class GachaBanner { public GachaInfo toProto() { return toProto(""); } + public GachaInfo toProto(String sessionKey) { - String record = "http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://" - + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() ? - Grasscutter.getConfig().getDispatchOptions().Ip : - Grasscutter.getConfig().getDispatchOptions().PublicIp) - + ":" - + Integer.toString(Grasscutter.getConfig().getDispatchOptions().PublicPort == 0 ? - Grasscutter.getConfig().getDispatchOptions().Port : - Grasscutter.getConfig().getDispatchOptions().PublicPort) + String record = "http" + (DISPATCH_INFO.encryption.useInRouting ? "s" : "") + "://" + + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" + + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + "/gacha?s=" + sessionKey + "&gachaType=" + gachaType; // Grasscutter.getLogger().info("record = " + record); GachaInfo.Builder info = GachaInfo.newBuilder() diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index ca7640e17..03edca09a 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -34,20 +34,22 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import org.greenrobot.eventbus.Subscribe; +import static emu.grasscutter.Configuration.*; + public class GachaManager { private final GameServer server; private final Int2ObjectMap gachaBanners; private GetGachaInfoRsp cachedProto; WatchService watchService; - private int[] yellowAvatars = new int[] {1003, 1016, 1042, 1035, 1041}; - private int[] yellowWeapons = new int[] {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502}; - private int[] purpleAvatars = new int[] {1006, 1014, 1015, 1020, 1021, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064}; - private int[] purpleWeapons = new int[] {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405}; - private int[] blueWeapons = new int[] {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304}; + private final int[] yellowAvatars = new int[] {1003, 1016, 1042, 1035, 1041}; + private final int[] yellowWeapons = new int[] {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502}; + private final int[] purpleAvatars = new int[] {1006, 1014, 1015, 1020, 1021, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064}; + private final int[] purpleWeapons = new int[] {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405}; + private final int[] blueWeapons = new int[] {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304}; - private static int starglitterId = 221; - private static int stardustId = 222; + private static final int starglitterId = 221; + private static final int stardustId = 222; public GachaManager(GameServer server) { this.server = server; @@ -73,7 +75,7 @@ public class GachaManager { } public synchronized void load() { - try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "Banners.json")) { + try (FileReader fileReader = new FileReader(DATA("Banners.json"))) { getGachaBanners().clear(); List banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, GachaBanner.class).getType()); if(banners.size() > 0) { @@ -242,15 +244,9 @@ public class GachaManager { } else { // Is weapon switch (itemData.getRankLevel()) { - case 5: - addStarglitter = 10; - break; - case 4: - addStarglitter = 2; - break; - case 3: - addStardust = 15; - break; + case 5 -> addStarglitter = 10; + case 4 -> addStarglitter = 2; + case 3 -> addStardust = 15; } } @@ -290,7 +286,7 @@ public class GachaManager { if(this.watchService == null) { try { this.watchService = FileSystems.getDefault().newWatchService(); - Path path = new File(Grasscutter.getConfig().DATA_FOLDER).toPath(); + Path path = new File(DATA_FOLDER).toPath(); path.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"); @@ -303,7 +299,7 @@ public class GachaManager { @Subscribe public synchronized void watchBannerJson(GameServerTickEvent tickEvent) { - if(Grasscutter.getConfig().getGameServerOptions().WatchGacha) { + if(GAME_OPTIONS.watchGachaConfig) { try { WatchKey watchKey = watchService.take(); diff --git a/src/main/java/emu/grasscutter/game/inventory/Inventory.java b/src/main/java/emu/grasscutter/game/inventory/Inventory.java index c4158ee6f..4a217ba54 100644 --- a/src/main/java/emu/grasscutter/game/inventory/Inventory.java +++ b/src/main/java/emu/grasscutter/game/inventory/Inventory.java @@ -6,7 +6,6 @@ import java.util.LinkedList; import java.util.List; import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.def.AvatarCostumeData; import emu.grasscutter.data.def.AvatarData; @@ -15,7 +14,6 @@ import emu.grasscutter.data.def.ItemData; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.avatar.AvatarStorage; import emu.grasscutter.game.avatar.Avatar; -import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; @@ -28,6 +26,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import static emu.grasscutter.Configuration.*; + public class Inventory implements Iterable { private final Player player; @@ -39,10 +39,10 @@ public class Inventory implements Iterable { this.store = new Long2ObjectOpenHashMap<>(); this.inventoryTypes = new Int2ObjectOpenHashMap<>(); - this.createInventoryTab(ItemType.ITEM_WEAPON, new EquipInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitWeapon)); - this.createInventoryTab(ItemType.ITEM_RELIQUARY, new EquipInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitRelic)); - this.createInventoryTab(ItemType.ITEM_MATERIAL, new MaterialInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitMaterial)); - this.createInventoryTab(ItemType.ITEM_FURNITURE, new MaterialInventoryTab(Grasscutter.getConfig().getGameServerOptions().InventoryLimitFurniture)); + this.createInventoryTab(ItemType.ITEM_WEAPON, new EquipInventoryTab(INVENTORY_LIMITS.weapons)); + this.createInventoryTab(ItemType.ITEM_RELIQUARY, new EquipInventoryTab(INVENTORY_LIMITS.relics)); + this.createInventoryTab(ItemType.ITEM_MATERIAL, new MaterialInventoryTab(INVENTORY_LIMITS.materials)); + this.createInventoryTab(ItemType.ITEM_FURNITURE, new MaterialInventoryTab(INVENTORY_LIMITS.furniture)); } public Player getPlayer() { @@ -242,24 +242,18 @@ public class Inventory implements Iterable { private void addVirtualItem(int itemId, int count) { switch (itemId) { - case 101: // Character exp - getPlayer().getServer().getInventoryManager().upgradeAvatar(player, getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), count); - break; - case 102: // Adventure exp - getPlayer().addExpDirectly(count); - break; - case 105: // Companionship exp - getPlayer().getServer().getInventoryManager().upgradeAvatarFetterLevel(player, getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), count); - break; - case 201: // Primogem - getPlayer().setPrimogems(player.getPrimogems() + count); - break; - case 202: // Mora - getPlayer().setMora(player.getMora() + count); - break; - case 203: // Genesis Crystals - getPlayer().setCrystals(player.getCrystals() + count); - break; + case 101 -> // Character exp + getPlayer().getServer().getInventoryManager().upgradeAvatar(player, getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), count); + case 102 -> // Adventure exp + getPlayer().addExpDirectly(count); + case 105 -> // Companionship exp + getPlayer().getServer().getInventoryManager().upgradeAvatarFetterLevel(player, getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), count); + case 201 -> // Primogem + getPlayer().setPrimogems(player.getPrimogems() + count); + case 202 -> // Mora + getPlayer().setMora(player.getMora() + count); + case 203 -> // Genesis Crystals + getPlayer().setCrystals(player.getCrystals() + count); } } diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index 8737d9755..86006e109 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -20,6 +20,8 @@ import org.jetbrains.annotations.NotNull; import java.lang.Math; import java.util.*; +import static emu.grasscutter.Configuration.*; + public class StaminaManager { // TODO: Skiff state detection? @@ -224,9 +226,10 @@ public class StaminaManager { // Returns new stamina and sends PlayerPropNotify public int setStamina(GameSession session, String reason, int newStamina) { - if (!Grasscutter.getConfig().OpenStamina) { + if (!GAME_OPTIONS.staminaUsage) { newStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); } + // set stamina player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 477f974ea..fd5343be8 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -60,6 +60,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import java.util.*; import java.util.concurrent.LinkedBlockingQueue; +import static emu.grasscutter.Configuration.*; + @Entity(value = "players", useDiscriminator = false) public class Player { @@ -353,7 +355,7 @@ public class Player { } private float getExpModifier() { - return Grasscutter.getConfig().getGameServerOptions().getGameRates().ADVENTURE_EXP_RATE; + return GAME_OPTIONS.rates.adventureExp; } // Affected by exp rate @@ -1218,7 +1220,7 @@ public class Player { } else if (prop == PlayerProperty.PROP_LAST_CHANGE_AVATAR_TIME) { // 10001 // TODO: implement sanity check } else if (prop == PlayerProperty.PROP_MAX_SPRING_VOLUME) { // 10002 - if (!(value >= 0 && value <= getSotSManager().GlobalMaximumSpringVolume)) { return false; } + if (!(value >= 0 && value <= SotSManager.GlobalMaximumSpringVolume)) { return false; } } else if (prop == PlayerProperty.PROP_CUR_SPRING_VOLUME) { // 10003 int playerMaximumSpringVolume = getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME); if (!(value >= 0 && value <= playerMaximumSpringVolume)) { return false; } @@ -1235,7 +1237,7 @@ public class Player { } else if (prop == PlayerProperty.PROP_IS_TRANSFERABLE) { // 10009 if (!(0 <= value && value <= 1)) { return false; } } else if (prop == PlayerProperty.PROP_MAX_STAMINA) { // 10010 - if (!(value >= 0 && value <= getStaminaManager().GlobalMaximumStamina)) { return false; } + if (!(value >= 0 && value <= StaminaManager.GlobalMaximumStamina)) { return false; } } else if (prop == PlayerProperty.PROP_CUR_PERSIST_STAMINA) { // 10011 int playerMaximumStamina = getProperty(PlayerProperty.PROP_MAX_STAMINA); if (!(value >= 0 && value <= playerMaximumStamina)) { return false; } diff --git a/src/main/java/emu/grasscutter/game/player/TeamInfo.java b/src/main/java/emu/grasscutter/game/player/TeamInfo.java index 5794a7913..7d1232e50 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamInfo.java +++ b/src/main/java/emu/grasscutter/game/player/TeamInfo.java @@ -4,10 +4,10 @@ import java.util.ArrayList; import java.util.List; import dev.morphia.annotations.Entity; -import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; import emu.grasscutter.game.avatar.Avatar; +import static emu.grasscutter.Configuration.*; + @Entity public class TeamInfo { private String name; @@ -15,7 +15,7 @@ public class TeamInfo { public TeamInfo() { this.name = ""; - this.avatars = new ArrayList<>(Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam); + this.avatars = new ArrayList<>(GAME_OPTIONS.avatarLimits.singlePlayerTeam); } public TeamInfo(List avatars) { @@ -44,7 +44,7 @@ public class TeamInfo { } public boolean addAvatar(Avatar avatar) { - if (size() >= Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam || contains(avatar)) { + if (size() >= GAME_OPTIONS.avatarLimits.singlePlayerTeam || contains(avatar)) { return false; } @@ -64,7 +64,7 @@ public class TeamInfo { } public void copyFrom(TeamInfo team) { - copyFrom(team, Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam); + copyFrom(team, GAME_OPTIONS.avatarLimits.singlePlayerTeam); } public void copyFrom(TeamInfo team, int maxTeamSize) { diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index 891d0a215..71961645f 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -5,7 +5,6 @@ import java.util.*; import dev.morphia.annotations.Entity; import dev.morphia.annotations.Transient; import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; import emu.grasscutter.data.def.AvatarSkillDepotData; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.entity.EntityAvatar; @@ -40,6 +39,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; +import static emu.grasscutter.Configuration.*; + @Entity public class TeamManager { @Transient private Player player; @@ -174,13 +175,14 @@ public class TeamManager { public int getMaxTeamSize() { if (getPlayer().isInMultiplayer()) { - int max = Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeamMultiplayer; + int max = GAME_OPTIONS.avatarLimits.multiplayerTeam; if (getPlayer().getWorld().getHost() == this.getPlayer()) { return Math.max(1, (int) Math.ceil(max / (double) getWorld().getPlayerCount())); } return Math.max(1, (int) Math.floor(max / (double) getWorld().getPlayerCount())); } - return Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam; + + return GAME_OPTIONS.avatarLimits.singlePlayerTeam; } // Methods @@ -236,7 +238,7 @@ public class TeamManager { // Add back entities into team for (int i = 0; i < this.getCurrentTeamInfo().getAvatars().size(); i++) { int avatarId = this.getCurrentTeamInfo().getAvatars().get(i); - EntityAvatar entity = null; + EntityAvatar entity; if (existingAvatars.containsKey(avatarId)) { entity = existingAvatars.get(avatarId); @@ -303,8 +305,8 @@ public class TeamManager { // Set team data LinkedHashSet newTeam = new LinkedHashSet<>(); - for (int i = 0; i < list.size(); i++) { - Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i)); + for (Long aLong : list) { + Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(aLong); if (avatar == null || newTeam.contains(avatar)) { // Should never happen return; @@ -339,8 +341,8 @@ public class TeamManager { // Set team data LinkedHashSet newTeam = new LinkedHashSet<>(); - for (int i = 0; i < list.size(); i++) { - Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i)); + for (Long aLong : list) { + Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(aLong); if (avatar == null || newTeam.contains(avatar)) { // Should never happen return; @@ -359,7 +361,7 @@ public class TeamManager { } public void setupTemporaryTeam(List> guidList) { - var team = guidList.stream().map(list -> { + this.temporaryTeam = guidList.stream().map(list -> { // Sanity checks if (list.size() == 0 || list.size() > getMaxTeamSize()) { return null; @@ -367,8 +369,8 @@ public class TeamManager { // Set team data LinkedHashSet newTeam = new LinkedHashSet<>(); - for (int i = 0; i < list.size(); i++) { - Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i)); + for (Long aLong : list) { + Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(aLong); if (avatar == null || newTeam.contains(avatar)) { // Should never happen return null; @@ -384,7 +386,6 @@ public class TeamManager { .filter(Objects::nonNull) .map(TeamInfo::new) .toList(); - this.temporaryTeam = team; } public void useTemporaryTeam(int index) { diff --git a/src/main/java/emu/grasscutter/game/shop/ShopManager.java b/src/main/java/emu/grasscutter/game/shop/ShopManager.java index 2c5d014f5..a27011012 100644 --- a/src/main/java/emu/grasscutter/game/shop/ShopManager.java +++ b/src/main/java/emu/grasscutter/game/shop/ShopManager.java @@ -16,6 +16,8 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; +import static emu.grasscutter.Configuration.*; + public class ShopManager { private final GameServer server; @@ -56,7 +58,7 @@ public class ShopManager { } private void loadShop() { - try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "Shop.json")) { + try (FileReader fileReader = new FileReader(DATA("Shop.json"))) { getShopData().clear(); List banners = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ShopTable.class).getType()); if(banners.size() > 0) { @@ -84,7 +86,7 @@ public class ShopManager { Grasscutter.getLogger().error("Unable to load shop data. Shop data size is 0."); } - if (Grasscutter.getConfig().getGameServerOptions().EnableOfficialShop) { + if (GAME_OPTIONS.enableShopItems) { GameData.getShopGoodsDataEntries().forEach((k, v) -> { if (!getShopData().containsKey(k.intValue())) getShopData().put(k.intValue(), new ArrayList<>()); @@ -100,7 +102,7 @@ public class ShopManager { } private void loadShopChest() { - try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "ShopChest.json")) { + try (FileReader fileReader = new FileReader(DATA("ShopChest.json"))) { getShopChestData().clear(); List shopChestTableList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ShopChestTable.class).getType()); if (shopChestTableList.size() > 0) { @@ -115,7 +117,7 @@ public class ShopManager { } private void loadShopChestBatchUse() { - try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "ShopChestBatchUse.json")) { + try (FileReader fileReader = new FileReader(DATA("ShopChestBatchUse.json"))) { getShopChestBatchUseData().clear(); List shopChestBatchUseTableList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, ShopChestBatchUseTable.class).getType()); if (shopChestBatchUseTableList.size() > 0) { diff --git a/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java b/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java index 952acd806..ae756f009 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerScheduleManager.java @@ -8,6 +8,8 @@ import emu.grasscutter.server.game.GameServer; import java.io.FileReader; import java.util.List; +import static emu.grasscutter.Configuration.*; + public class TowerScheduleManager { private final GameServer gameServer; @@ -23,9 +25,8 @@ public class TowerScheduleManager { private TowerScheduleConfig towerScheduleConfig; public synchronized void load(){ - try (FileReader fileReader = new FileReader(Grasscutter.getConfig().DATA_FOLDER + "TowerSchedule.json")) { + try (FileReader fileReader = new FileReader(DATA("TowerSchedule.json"))) { towerScheduleConfig = Grasscutter.getGsonFactory().fromJson(fileReader, TowerScheduleConfig.class); - } catch (Exception e) { Grasscutter.getLogger().error("Unable to load tower schedule config.", e); } @@ -40,6 +41,7 @@ public class TowerScheduleManager { if(data == null){ Grasscutter.getLogger().error("Could not get current tower schedule data by config:{}", towerScheduleConfig); } + return data; } @@ -51,28 +53,31 @@ public class TowerScheduleManager { var entranceFloors = getCurrentTowerScheduleData().getEntranceFloorId(); var scheduleFloors = getScheduleFloors(); var nextId = 0; + // find in entrance floors first for(int i=0;i Objects.equals(x, req.baseUrl()))) { - Grasscutter.getLogger().info(translate("messages.dispatch.request", req.ip(), req.method(), req.baseUrl()) + (Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING ? "(MISSING)" : "")); + if(SERVER.debugLevel == ServerDebugMode.MISSING && Arrays.stream(missingRoutes).anyMatch(x -> Objects.equals(x, req.baseUrl()))) { + Grasscutter.getLogger().info(translate("messages.dispatch.request", req.ip(), req.method(), req.baseUrl()) + (SERVER.debugLevel == ServerDebugMode.MISSING ? "(MISSING)" : "")); } res.send(response); } diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index f7f14019f..3ae4ea08a 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -4,7 +4,6 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.protobuf.ByteString; -import emu.grasscutter.Config; import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter.ServerDebugMode; import emu.grasscutter.Grasscutter.ServerRunMode; @@ -33,9 +32,11 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; import java.io.*; import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.*; import static emu.grasscutter.utils.Language.translate; +import static emu.grasscutter.Configuration.*; public final class DispatchServer { public static String query_region_list = ""; @@ -64,7 +65,7 @@ public final class DispatchServer { public void setHttpServer(Express httpServer) { this.httpServer.stop(); this.httpServer = httpServer; - this.httpServer.listen(Grasscutter.getConfig().getDispatchOptions().Port); + this.httpServer.listen(DISPATCH_INFO.bindPort); } public Gson getGsonFactory() { @@ -73,7 +74,7 @@ public final class DispatchServer { public QueryCurrRegionHttpRsp getCurrRegion() { // Needs to be fixed by having the game servers connect to the dispatch server. - if (Grasscutter.getConfig().RunMode == ServerRunMode.HYBRID) { + if (SERVER.runMode == ServerRunMode.HYBRID) { return regions.get(defaultServerName).parsedRegionQuery; } @@ -84,14 +85,14 @@ public final class DispatchServer { public void loadQueries() { File file; - file = new File(Grasscutter.getConfig().DATA_FOLDER + "query_region_list.txt"); + file = new File(DATA("query_region_list.txt")); if (file.exists()) { query_region_list = new String(FileUtils.read(file)); } else { Grasscutter.getLogger().warn("[Dispatch] query_region_list not found! Using default region list."); } - file = new File(Grasscutter.getConfig().DATA_FOLDER + "query_cur_region.txt"); + file = new File(DATA("query_cur_region.txt")); if (file.exists()) { query_cur_region = new String(FileUtils.read(file)); } else { @@ -108,52 +109,37 @@ public final class DispatchServer { QueryCurrRegionHttpRsp regionQuery = QueryCurrRegionHttpRsp.parseFrom(decoded2); List servers = new ArrayList<>(); - List usedNames = new ArrayList<>(); // List to check for potential naming conflicts - if (Grasscutter.getConfig().RunMode == ServerRunMode.HYBRID) { // Automatically add the game server if in - // hybrid mode + List usedNames = new ArrayList<>(); // List to check for potential naming conflicts. + if (SERVER.runMode == ServerRunMode.HYBRID) { // Automatically add the game server if in hybrid mode. RegionSimpleInfo server = RegionSimpleInfo.newBuilder() .setName("os_usa") - .setTitle(Grasscutter.getConfig().getGameServerOptions().Name) + .setTitle(DISPATCH_INFO.defaultName) .setType("DEV_PUBLIC") .setDispatchUrl( - "http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://" - + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() - ? Grasscutter.getConfig().getDispatchOptions().Ip - : Grasscutter.getConfig().getDispatchOptions().PublicIp) - + ":" - + (Grasscutter.getConfig().getDispatchOptions().PublicPort != 0 - ? Grasscutter.getConfig().getDispatchOptions().PublicPort - : Grasscutter.getConfig().getDispatchOptions().Port) + "http" + (DISPATCH_ENCRYPTION.useEncryption ? "s" : "") + "://" + + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" + + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + "/query_cur_region/" + defaultServerName) .build(); usedNames.add(defaultServerName); servers.add(server); RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder() - .setGateserverIp((Grasscutter.getConfig().getGameServerOptions().PublicIp.isEmpty() - ? Grasscutter.getConfig().getGameServerOptions().Ip - : Grasscutter.getConfig().getGameServerOptions().PublicIp)) - .setGateserverPort(Grasscutter.getConfig().getGameServerOptions().PublicPort != 0 - ? Grasscutter.getConfig().getGameServerOptions().PublicPort - : Grasscutter.getConfig().getGameServerOptions().Port) - .setSecretKey(ByteString - .copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin"))) + .setGateserverIp(lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress)) + .setGateserverPort(lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort)) + .setSecretKey(ByteString.copyFrom(FileUtils.read(KEYS_FOLDER + "/dispatchSeed.bin"))) .build(); QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(serverRegion).build(); regions.put(defaultServerName, new RegionData(parsedRegionQuery, Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray()))); - } else { - if (Grasscutter.getConfig().getDispatchOptions().getGameServers().length == 0) { - Grasscutter.getLogger() - .error("[Dispatch] There are no game servers available. Exiting due to unplayable state."); - System.exit(1); - } + } else if (DISPATCH_INFO.regions.length == 0) { + Grasscutter.getLogger().error("[Dispatch] There are no game servers available. Exiting due to unplayable state."); + System.exit(1); } - for (Config.DispatchServerOptions.RegionInfo regionInfo : Grasscutter.getConfig().getDispatchOptions() - .getGameServers()) { + for (var regionInfo : DISPATCH_INFO.regions) { if (usedNames.contains(regionInfo.Name)) { Grasscutter.getLogger().error("Region name already in use."); continue; @@ -163,13 +149,10 @@ public final class DispatchServer { .setTitle(regionInfo.Title) .setType("DEV_PUBLIC") .setDispatchUrl( - "http" + (Grasscutter.getConfig().getDispatchOptions().FrontHTTPS ? "s" : "") + "://" - + (Grasscutter.getConfig().getDispatchOptions().PublicIp.isEmpty() - ? Grasscutter.getConfig().getDispatchOptions().Ip - : Grasscutter.getConfig().getDispatchOptions().PublicIp) - + ":" + (Grasscutter.getConfig().getDispatchOptions().PublicPort != 0 - ? Grasscutter.getConfig().getDispatchOptions().PublicPort - : Grasscutter.getConfig().getDispatchOptions().Port) + "/query_cur_region/" + regionInfo.Name) + "http" + (DISPATCH_ENCRYPTION.useEncryption ? "s" : "") + "://" + + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" + + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + + "/query_cur_region/" + regionInfo.Name) .build(); usedNames.add(regionInfo.Name); servers.add(server); @@ -178,7 +161,7 @@ public final class DispatchServer { .setGateserverIp(regionInfo.Ip) .setGateserverPort(regionInfo.Port) .setSecretKey(ByteString - .copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin"))) + .copyFrom(FileUtils.read(KEYS_FOLDER + "/dispatchSeed.bin"))) .build(); QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(serverRegion).build(); @@ -194,8 +177,8 @@ public final class DispatchServer { .build(); this.regionListBase64 = Base64.getEncoder().encodeToString(regionList.toByteString().toByteArray()); - } catch (Exception e) { - Grasscutter.getLogger().error("[Dispatch] Error while initializing region info!", e); + } catch (Exception exception) { + Grasscutter.getLogger().error("[Dispatch] Error while initializing region info!", exception); } } @@ -205,14 +188,14 @@ public final class DispatchServer { Server server = new Server(); ServerConnector serverConnector; - if(Grasscutter.getConfig().getDispatchOptions().UseSSL) { + if(DISPATCH_ENCRYPTION.useEncryption) { SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); - File keystoreFile = new File(Grasscutter.getConfig().getDispatchOptions().KeystorePath); + File keystoreFile = new File(DISPATCH_ENCRYPTION.keystore); if(keystoreFile.exists()) { try { sslContextFactory.setKeyStorePath(keystoreFile.getPath()); - sslContextFactory.setKeyStorePassword(Grasscutter.getConfig().getDispatchOptions().KeystorePassword); + sslContextFactory.setKeyStorePassword(DISPATCH_ENCRYPTION.keystorePassword); } catch (Exception e) { e.printStackTrace(); Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.password_error")); @@ -230,7 +213,7 @@ public final class DispatchServer { serverConnector = new ServerConnector(server, sslContextFactory); } else { Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.no_keystore_error")); - Grasscutter.getConfig().getDispatchOptions().UseSSL = false; + DISPATCH_ENCRYPTION.useEncryption = false; serverConnector = new ServerConnector(server); } @@ -238,24 +221,27 @@ public final class DispatchServer { serverConnector = new ServerConnector(server); } - serverConnector.setPort(Grasscutter.getConfig().getDispatchOptions().Port); + serverConnector.setPort(DISPATCH_INFO.bindPort); server.setConnectors(new Connector[]{serverConnector}); return server; }); - config.enforceSsl = Grasscutter.getConfig().getDispatchOptions().UseSSL; - if(Grasscutter.getConfig().DebugMode == ServerDebugMode.ALL) { + config.enforceSsl = DISPATCH_ENCRYPTION.useEncryption; + if(SERVER.debugLevel == ServerDebugMode.ALL) { config.enableDevLogging(); } - if (Grasscutter.getConfig().getDispatchOptions().CORS){ - if (Grasscutter.getConfig().getDispatchOptions().CORSAllowedOrigins.length > 0) config.enableCorsForOrigin(Grasscutter.getConfig().getDispatchOptions().CORSAllowedOrigins); + + if (DISPATCH_POLICIES.cors.enabled) { + var corsPolicy = DISPATCH_POLICIES.cors; + if (corsPolicy.allowedOrigins.length > 0) + config.enableCorsForOrigin(corsPolicy.allowedOrigins); else config.enableCorsForAllOrigins(); } }); httpServer.get("/", (req, res) -> res.send("" + translate("messages.status.welcome") + "")); httpServer.raw().error(404, ctx -> { - if(Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING) { + if(SERVER.debugLevel == ServerDebugMode.MISSING) { Grasscutter.getLogger().info(translate("messages.dispatch.unhandled_request_error", ctx.method(), ctx.url())); } ctx.contentType("text/html"); @@ -450,7 +436,7 @@ public final class DispatchServer { httpServer.get("/admin/mi18n/plat_oversea/m202003048/m202003048-version.json", new DispatchHttpJsonHandler("{\"version\":51}")); // gacha record. - String gachaMappingsPath = Utils.toFilePath(Grasscutter.getConfig().DATA_FOLDER + "/gacha_mappings.js"); + String gachaMappingsPath = Utils.toFilePath(DATA("/gacha_mappings.js")); // TODO: Only serve the html page and have a subsequent request to fetch the gacha data. httpServer.get("/gacha", new GachaRecordHandler()); if(!(new File(gachaMappingsPath).exists())) { @@ -462,7 +448,7 @@ public final class DispatchServer { // static file support for plugins httpServer.raw().config.precompressStaticFiles = false; // If this isn't set to false, files such as images may appear corrupted when serving static files - httpServer.listen(Grasscutter.getConfig().getDispatchOptions().Port); + httpServer.listen(DISPATCH_INFO.bindPort); Grasscutter.getLogger().info(translate("messages.dispatch.port_bind", Integer.toString(httpServer.raw().port()))); } @@ -481,15 +467,11 @@ public final class DispatchServer { if (next > last) { int eqPos = qs.indexOf('=', last); - try { - if (eqPos < 0 || eqPos > next) { - result.put(URLDecoder.decode(qs.substring(last, next), "utf-8"), ""); - } else { - result.put(URLDecoder.decode(qs.substring(last, eqPos), "utf-8"), - URLDecoder.decode(qs.substring(eqPos + 1, next), "utf-8")); - } - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); // will never happen, utf-8 support is mandatory for java + if (eqPos < 0 || eqPos > next) { + result.put(URLDecoder.decode(qs.substring(last, next), StandardCharsets.UTF_8), ""); + } else { + result.put(URLDecoder.decode(qs.substring(last, eqPos), StandardCharsets.UTF_8), + URLDecoder.decode(qs.substring(eqPos + 1, next), StandardCharsets.UTF_8)); } } last = next + 1; diff --git a/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java b/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java index e5a4ca055..67b3d4023 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java +++ b/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java @@ -9,6 +9,7 @@ import express.http.Request; import express.http.Response; import static emu.grasscutter.utils.Language.translate; +import static emu.grasscutter.Configuration.*; public class DefaultAuthenticationHandler implements AuthenticationHandler { @@ -37,11 +38,11 @@ public class DefaultAuthenticationHandler implements AuthenticationHandler { // Check if account exists, else create a new one. if (account == null) { // Account doesn't exist, so we can either auto create it if the config value is set. - if (Grasscutter.getConfig().getDispatchOptions().AutomaticallyCreateAccounts) { + if (ACCOUNT.autoCreate) { // This account has been created AUTOMATICALLY. There will be no permissions added. account = DatabaseHelper.createAccountWithId(requestData.account, 0); - for (String permission : Grasscutter.getConfig().getDispatchOptions().defaultPermissions) { + for (String permission : ACCOUNT.defaultPermissions) { account.addPermission(permission); } diff --git a/src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java b/src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java index 8676574bb..b90510367 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java +++ b/src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java @@ -3,7 +3,6 @@ package emu.grasscutter.server.dispatch.http; import java.io.File; import java.io.IOException; -import emu.grasscutter.Grasscutter; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.Account; import emu.grasscutter.utils.FileUtils; @@ -12,10 +11,12 @@ import express.http.HttpContextHandler; import express.http.Request; import express.http.Response; +import static emu.grasscutter.Configuration.*; + public final class GachaRecordHandler implements HttpContextHandler { String render_template; public GachaRecordHandler() { - File template = new File(Utils.toFilePath(Grasscutter.getConfig().DATA_FOLDER + "/gacha_records.html")); + File template = new File(Utils.toFilePath(DATA("/gacha_records.html"))); if (template.exists()) { // Load from cache render_template = new String(FileUtils.read(template)); @@ -31,11 +32,11 @@ public final class GachaRecordHandler implements HttpContextHandler { int page = 0; int gachaType = 0; if (req.query("p") != null) { - page = Integer.valueOf(req.query("p")); + page = Integer.parseInt(req.query("p")); } if (req.query("gachaType") != null) { - gachaType = Integer.valueOf(req.query("gachaType")); + gachaType = Integer.parseInt(req.query("gachaType")); } Account account = DatabaseHelper.getAccountBySessionKey(sessionKey); diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index cb0e4965d..71e1cf856 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -30,11 +30,9 @@ import java.net.InetSocketAddress; import java.time.OffsetDateTime; import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import static emu.grasscutter.utils.Language.translate; +import static emu.grasscutter.Configuration.*; public final class GameServer extends KcpServer { private final InetSocketAddress address; @@ -59,8 +57,8 @@ public final class GameServer extends KcpServer { public GameServer() { this(new InetSocketAddress( - Grasscutter.getConfig().getGameServerOptions().Ip, - Grasscutter.getConfig().getGameServerOptions().Port + GAME_INFO.bindAddress, + GAME_INFO.bindPort )); } diff --git a/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java b/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java index 88e7fa17f..4bba854ef 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java +++ b/src/main/java/emu/grasscutter/server/game/GameServerPacketHandler.java @@ -14,6 +14,8 @@ import emu.grasscutter.server.game.GameSession.SessionState; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import static emu.grasscutter.Configuration.*; + @SuppressWarnings("unchecked") public class GameServerPacketHandler { private final Int2ObjectMap handlers; @@ -92,7 +94,7 @@ public class GameServerPacketHandler { } // Log unhandled packets - if (Grasscutter.getConfig().DebugMode == ServerDebugMode.MISSING) { + if (SERVER.debugLevel == ServerDebugMode.MISSING) { Grasscutter.getLogger().info("Unhandled packet (" + opcode + "): " + emu.grasscutter.net.packet.PacketOpcodesUtil.getOpcodeName(opcode)); } } diff --git a/src/main/java/emu/grasscutter/server/game/GameSession.java b/src/main/java/emu/grasscutter/server/game/GameSession.java index d1d7eef01..7cc9a799f 100644 --- a/src/main/java/emu/grasscutter/server/game/GameSession.java +++ b/src/main/java/emu/grasscutter/server/game/GameSession.java @@ -3,7 +3,6 @@ package emu.grasscutter.server.game; import java.io.File; import java.net.InetSocketAddress; import java.nio.ByteBuffer; -import java.util.HashSet; import java.util.Set; import emu.grasscutter.Grasscutter; @@ -23,9 +22,10 @@ import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import static emu.grasscutter.utils.Language.translate; +import static emu.grasscutter.Configuration.*; public class GameSession extends KcpChannel { - private GameServer server; + private final GameServer server; private Account account; private Player player; @@ -140,7 +140,7 @@ public class GameSession extends KcpChannel { } public void replayPacket(int opcode, String name) { - String filePath = Grasscutter.getConfig().PACKETS_FOLDER + name; + String filePath = PACKETS_FOLDER + name; File p = new File(filePath); if (!p.exists()) return; @@ -172,7 +172,7 @@ public class GameSession extends KcpChannel { } // Log - if (Grasscutter.getConfig().DebugMode == ServerDebugMode.ALL) { + if (SERVER.debugLevel == ServerDebugMode.ALL) { logPacket(packet); } @@ -239,7 +239,7 @@ public class GameSession extends KcpChannel { } // Log packet - if (Grasscutter.getConfig().DebugMode == ServerDebugMode.ALL) { + if (SERVER.debugLevel == ServerDebugMode.ALL) { if (!loopPacket.contains(opcode)) { Grasscutter.getLogger().info("RECV: " + PacketOpcodesUtil.getOpcodeName(opcode) + " (" + opcode + ")"); System.out.println(Utils.bytesToHex(payload)); diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java index 53d141a99..ec591c91f 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetPlayerBornDataReq.java @@ -19,6 +19,8 @@ import emu.grasscutter.server.game.GameSession.SessionState; import java.util.Arrays; +import static emu.grasscutter.Configuration.*; + @Opcodes(PacketOpcodes.SetPlayerBornDataReq) public class HandlerSetPlayerBornDataReq extends PacketHandler { @@ -85,12 +87,13 @@ public class HandlerSetPlayerBornDataReq extends PacketHandler { session.send(new BasePacket(PacketOpcodes.SetPlayerBornDataRsp)); // Default mail + var welcomeMail = GAME_INFO.joinOptions.welcomeMail; MailBuilder mailBuilder = new MailBuilder(player.getUid(), new Mail()); - mailBuilder.mail.mailContent.title = Grasscutter.getConfig().GameServer.WelcomeMailTitle; - mailBuilder.mail.mailContent.sender = Grasscutter.getConfig().GameServer.WelcomeMailSender; + mailBuilder.mail.mailContent.title = welcomeMail.title; + mailBuilder.mail.mailContent.sender = welcomeMail.sender; // Please credit Grasscutter if changing something here. We don't condone commercial use of the project. - mailBuilder.mail.mailContent.content = Grasscutter.getConfig().GameServer.WelcomeMailContent + "\n"; - mailBuilder.mail.itemList.addAll(Arrays.asList(Grasscutter.getConfig().GameServer.WelcomeMailItems)); + mailBuilder.mail.mailContent.content = welcomeMail.content + "\n"; + mailBuilder.mail.itemList.addAll(Arrays.asList(welcomeMail.items)); mailBuilder.mail.importance = 1; player.sendMail(mailBuilder.mail); } catch (Exception e) { diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java index ff373140b..a0948c737 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetPlayerFriendListRsp.java @@ -1,7 +1,6 @@ package emu.grasscutter.server.packet.send; import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; import emu.grasscutter.game.friends.Friendship; import emu.grasscutter.game.player.Player; import emu.grasscutter.net.packet.BasePacket; @@ -12,20 +11,23 @@ import emu.grasscutter.net.proto.GetPlayerFriendListRspOuterClass.GetPlayerFrien import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture; import emu.grasscutter.net.proto.PlatformTypeOuterClass; +import static emu.grasscutter.Configuration.*; + public class PacketGetPlayerFriendListRsp extends BasePacket { public PacketGetPlayerFriendListRsp(Player player) { super(PacketOpcodes.GetPlayerFriendListRsp); + var serverAccount = GAME_INFO.serverAccount; FriendBrief serverFriend = FriendBrief.newBuilder() .setUid(GameConstants.SERVER_CONSOLE_UID) - .setNickname(Grasscutter.getConfig().getGameServerOptions().ServerNickname) - .setLevel(Grasscutter.getConfig().getGameServerOptions().ServerLevel) - .setProfilePicture(ProfilePicture.newBuilder().setAvatarId(Grasscutter.getConfig().getGameServerOptions().ServerAvatarId)) - .setWorldLevel(Grasscutter.getConfig().getGameServerOptions().ServerWorldLevel) - .setSignature(Grasscutter.getConfig().getGameServerOptions().ServerSignature) + .setNickname(serverAccount.nickName) + .setLevel(serverAccount.adventureRank) + .setProfilePicture(ProfilePicture.newBuilder().setAvatarId(serverAccount.avatarId)) + .setWorldLevel(serverAccount.worldLevel) + .setSignature(serverAccount.signature) .setLastActiveTime((int) (System.currentTimeMillis() / 1000f)) - .setNameCardId(Grasscutter.getConfig().getGameServerOptions().ServerNameCardId) + .setNameCardId(serverAccount.nameCardId) .setOnlineState(FriendOnlineState.FRIEND_ONLINE) .setParam(1) .setIsGameSource(true) @@ -37,10 +39,12 @@ public class PacketGetPlayerFriendListRsp extends BasePacket { for (Friendship friendship : player.getFriendsList().getFriends().values()) { proto.addFriendList(friendship.toProto()); } + for (Friendship friendship : player.getFriendsList().getPendingFriends().values()) { if (friendship.getAskerId() == player.getUid()) { continue; } + proto.addAskFriendList(friendship.toProto()); } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java index 6407e9412..9a21e4143 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java @@ -2,7 +2,6 @@ package emu.grasscutter.server.packet.send; import com.google.protobuf.ByteString; import emu.grasscutter.Grasscutter; -import emu.grasscutter.Grasscutter.ServerDebugMode; import emu.grasscutter.Grasscutter.ServerRunMode; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; @@ -13,9 +12,10 @@ import emu.grasscutter.server.game.GameSession; import emu.grasscutter.utils.FileUtils; import java.io.File; -import java.net.URL; import java.util.Base64; +import static emu.grasscutter.Configuration.*; + public class PacketPlayerLoginRsp extends BasePacket { private static QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp regionCache; @@ -27,10 +27,10 @@ public class PacketPlayerLoginRsp extends BasePacket { RegionInfo info; - if (Grasscutter.getConfig().RunMode == ServerRunMode.GAME_ONLY) { + if (SERVER.runMode == ServerRunMode.GAME_ONLY) { if (regionCache == null) { try { - File file = new File(Grasscutter.getConfig().DATA_FOLDER + "query_cur_region.txt"); + File file = new File(DATA("query_cur_region.txt")); String query_cur_region = ""; if (file.exists()) { query_cur_region = new String(FileUtils.read(file)); @@ -42,9 +42,9 @@ public class PacketPlayerLoginRsp extends BasePacket { QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp regionQuery = QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp.parseFrom(decodedCurRegion); RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder() - .setGateserverIp((Grasscutter.getConfig().getGameServerOptions().PublicIp.isEmpty() ? Grasscutter.getConfig().getGameServerOptions().Ip : Grasscutter.getConfig().getGameServerOptions().PublicIp)) - .setGateserverPort(Grasscutter.getConfig().getGameServerOptions().PublicPort != 0 ? Grasscutter.getConfig().getGameServerOptions().PublicPort : Grasscutter.getConfig().getGameServerOptions().Port) - .setSecretKey(ByteString.copyFrom(FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin"))) + .setGateserverIp(lr(GAME_INFO.accessAddress, GAME_INFO.bindAddress)) + .setGateserverPort(lr(GAME_INFO.accessPort, GAME_INFO.bindPort)) + .setSecretKey(ByteString.copyFrom(FileUtils.read(KEYS_FOLDER + "/dispatchSeed.bin"))) .build(); regionCache = regionQuery.toBuilder().setRegionInfo(serverRegion).build(); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerStoreNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerStoreNotify.java index a3a6ff7b8..3ad196af3 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerStoreNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerStoreNotify.java @@ -1,7 +1,5 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.player.Player; import emu.grasscutter.net.packet.BasePacket; @@ -10,6 +8,8 @@ import emu.grasscutter.net.proto.ItemOuterClass.Item; import emu.grasscutter.net.proto.PlayerStoreNotifyOuterClass.PlayerStoreNotify; import emu.grasscutter.net.proto.StoreTypeOuterClass.StoreType; +import static emu.grasscutter.Configuration.*; + public class PacketPlayerStoreNotify extends BasePacket { public PacketPlayerStoreNotify(Player player) { @@ -19,7 +19,7 @@ public class PacketPlayerStoreNotify extends BasePacket { PlayerStoreNotify.Builder p = PlayerStoreNotify.newBuilder() .setStoreType(StoreType.STORE_PACK) - .setWeightLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitAll); + .setWeightLimit(GAME_OPTIONS.inventoryLimits.all); for (GameItem item : player.getInventory()) { Item itemProto = item.toProto(); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPullRecentChatRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPullRecentChatRsp.java index 871534b53..0e757d11b 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPullRecentChatRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPullRecentChatRsp.java @@ -1,42 +1,41 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.Config.GameServerOptions; import emu.grasscutter.game.player.Player; import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.ChatInfoOuterClass.ChatInfo; import emu.grasscutter.net.proto.PullRecentChatRspOuterClass.PullRecentChatRsp; import emu.grasscutter.utils.Utils; +import static emu.grasscutter.Configuration.*; + public class PacketPullRecentChatRsp extends BasePacket { public PacketPullRecentChatRsp(Player player) { super(PacketOpcodes.PullRecentChatRsp); - GameServerOptions serverOptions = Grasscutter.getConfig().getGameServerOptions(); + var joinOptions = GAME_INFO.joinOptions; PullRecentChatRsp.Builder proto = PullRecentChatRsp.newBuilder(); - if (serverOptions.WelcomeEmotes != null && serverOptions.WelcomeEmotes.length > 0) { + if (joinOptions.welcomeEmotes != null && joinOptions.welcomeEmotes.length > 0) { ChatInfo welcomeEmote = ChatInfo.newBuilder() .setTime((int) (System.currentTimeMillis() / 1000)) .setUid(GameConstants.SERVER_CONSOLE_UID) .setToUid(player.getUid()) - .setIcon(serverOptions.WelcomeEmotes[Utils.randomRange(0, serverOptions.WelcomeEmotes.length - 1)]) + .setIcon(joinOptions.welcomeEmotes[Utils.randomRange(0, joinOptions.welcomeEmotes.length - 1)]) .build(); proto.addChatInfo(welcomeEmote); } - if (serverOptions.WelcomeMotd != null && serverOptions.WelcomeMotd.length() > 0) { - ChatInfo welcomeMotd = ChatInfo.newBuilder() + if (joinOptions.welcomeMessage != null && joinOptions.welcomeMessage.length() > 0) { + ChatInfo welcomeMessage = ChatInfo.newBuilder() .setTime((int) (System.currentTimeMillis() / 1000)) .setUid(GameConstants.SERVER_CONSOLE_UID) .setToUid(player.getUid()) - .setText(Grasscutter.getConfig().getGameServerOptions().WelcomeMotd) + .setText(joinOptions.welcomeMessage) .build(); - - proto.addChatInfo(welcomeMotd); + proto.addChatInfo(welcomeMessage); } this.setData(proto); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketStoreWeightLimitNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketStoreWeightLimitNotify.java index 61b51948b..77f9da803 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketStoreWeightLimitNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketStoreWeightLimitNotify.java @@ -1,11 +1,12 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.Grasscutter; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.StoreTypeOuterClass.StoreType; import emu.grasscutter.net.proto.StoreWeightLimitNotifyOuterClass.StoreWeightLimitNotify; +import static emu.grasscutter.Configuration.*; + public class PacketStoreWeightLimitNotify extends BasePacket { public PacketStoreWeightLimitNotify() { @@ -13,11 +14,11 @@ public class PacketStoreWeightLimitNotify extends BasePacket { StoreWeightLimitNotify p = StoreWeightLimitNotify.newBuilder() .setStoreType(StoreType.STORE_PACK) - .setWeightLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitAll) - .setWeaponCountLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitWeapon) - .setReliquaryCountLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitRelic) - .setMaterialCountLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitMaterial) - .setFurnitureCountLimit(Grasscutter.getConfig().getGameServerOptions().InventoryLimitFurniture) + .setWeightLimit(INVENTORY_LIMITS.all) + .setWeaponCountLimit(INVENTORY_LIMITS.weapons) + .setReliquaryCountLimit(INVENTORY_LIMITS.relics) + .setMaterialCountLimit(INVENTORY_LIMITS.materials) + .setFurnitureCountLimit(INVENTORY_LIMITS.furniture) .build(); this.setData(p); diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index f69aafc80..4a5af6e49 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -1,12 +1,8 @@ package emu.grasscutter.tools; -import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.FilenameFilter; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; @@ -14,14 +10,12 @@ import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; -import java.util.stream.Collectors; import com.google.gson.reflect.TypeToken; import emu.grasscutter.GameConstants; import emu.grasscutter.Grasscutter; import emu.grasscutter.command.Command; -import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandMap; import emu.grasscutter.data.GameData; import emu.grasscutter.data.ResourceLoader; @@ -32,6 +26,7 @@ import emu.grasscutter.data.def.SceneData; import emu.grasscutter.utils.Utils; import static emu.grasscutter.utils.Language.translate; +import static emu.grasscutter.Configuration.*; public final class Tools { public static void createGmHandbook() throws Exception { @@ -42,50 +37,45 @@ public final class Tools { ToolsWithLanguageOption.createGachaMapping(location, getLanguageOption()); } - public static List getAvailableLanguage() throws Exception { - File textMapFolder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap"); - List availableLangList = new ArrayList(); - for (String textMapFileName : textMapFolder.list(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - if (name.startsWith("TextMap") && name.endsWith(".json")){ - return true; - } - return false; - } - })) { - availableLangList.add(textMapFileName.replace("TextMap","").replace(".json","").toLowerCase()); - } - return availableLangList; + public static List getAvailableLanguage() { + File textMapFolder = new File(RESOURCE("TextMap")); + List availableLangList = new ArrayList<>(); + for (String textMapFileName : Objects.requireNonNull(textMapFolder.list((dir, name) -> name.startsWith("TextMap") && name.endsWith(".json")))) { + availableLangList.add(textMapFileName.replace("TextMap", "").replace(".json", "").toLowerCase()); + } return availableLangList; } - public static String getLanguageOption() throws Exception { + public static String getLanguageOption() { List availableLangList = getAvailableLanguage(); // Use system out for better format if (availableLangList.size() == 1) { return availableLangList.get(0).toUpperCase(); } - String stagedMessage = ""; - stagedMessage += "The following languages mappings are available, please select one: [default: EN]\n"; - String groupedLangList = ">\t"; + StringBuilder stagedMessage = new StringBuilder(); + stagedMessage.append("The following languages mappings are available, please select one: [default: EN] \n"); + + StringBuilder groupedLangList = new StringBuilder(">\t"); String input; int groupedLangCount = 0; - String input = ""; + for (String availableLanguage: availableLangList){ groupedLangCount++; - groupedLangList = groupedLangList + "" + availableLanguage + "\t"; + groupedLangList.append(availableLanguage).append("\t"); + if (groupedLangCount == 6) { - stagedMessage += groupedLangList + "\n"; + stagedMessage.append(groupedLangList).append("\n"); groupedLangCount = 0; - groupedLangList = ">\t"; + groupedLangList = new StringBuilder(">\t"); } } - if (groupedLangCount > 0) { - stagedMessage += groupedLangList + "\n"; - } - stagedMessage += "\nYour choice:[EN] "; - input = Grasscutter.getConsole().readLine(stagedMessage); + if (groupedLangCount > 0) { + stagedMessage.append(groupedLangList).append("\n"); + } + + stagedMessage.append("\nYour choice:[EN] "); + + input = Grasscutter.getConsole().readLine(stagedMessage.toString()); if (availableLangList.contains(input.toLowerCase())) { return input.toUpperCase(); } @@ -101,7 +91,7 @@ final class ToolsWithLanguageOption { ResourceLoader.loadResources(); Map map; - try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap/TextMap"+language+".json")), StandardCharsets.UTF_8)) { + try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(RESOURCE("TextMap/TextMap"+language+".json"))), StandardCharsets.UTF_8)) { map = Grasscutter.getGsonFactory().fromJson(fileReader, new TypeToken>() {}.getType()); } @@ -119,9 +109,9 @@ final class ToolsWithLanguageOption { writer.println("// Commands"); for (Command cmd : cmdList) { - String cmdName = cmd.label(); + StringBuilder cmdName = new StringBuilder(cmd.label()); while (cmdName.length() <= 15) { - cmdName = " " + cmdName; + cmdName.insert(0, " "); } writer.println(cmdName + " : " + translate(cmd.description())); } @@ -178,16 +168,13 @@ final class ToolsWithLanguageOption { ResourceLoader.loadResources(); Map map; - try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(Grasscutter.getConfig().RESOURCE_FOLDER + "TextMap/TextMap"+language+".json")), StandardCharsets.UTF_8)) { + try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(RESOURCE("TextMap/TextMap" + language + ".json"))), StandardCharsets.UTF_8)) { map = Grasscutter.getGsonFactory().fromJson(fileReader, new TypeToken>() {}.getType()); } List list; - String fileName = location; - - try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(fileName), StandardCharsets.UTF_8), false)) { - + try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(location), StandardCharsets.UTF_8), false)) { list = new ArrayList<>(GameData.getAvatarDataMap().keySet()); Collections.sort(list); @@ -209,18 +196,11 @@ final class ToolsWithLanguageOption { } else { writer.print(","); } - String color; - switch (data.getQualityType()){ - case "QUALITY_PURPLE": - color = "purple"; - break; - case "QUALITY_ORANGE": - color = "yellow"; - break; - case "QUALITY_BLUE": - default: - color = "blue"; - } + String color = switch (data.getQualityType()) { + case "QUALITY_PURPLE" -> "purple"; + case "QUALITY_ORANGE" -> "yellow"; + default -> "blue"; + }; // Got the magic number 4233146695 from manually search in the json file writer.println( "\"" + (avatarID % 1000 + 1000) + "\" : [\"" diff --git a/src/main/java/emu/grasscutter/utils/Crypto.java b/src/main/java/emu/grasscutter/utils/Crypto.java index e6d260e94..188a7192e 100644 --- a/src/main/java/emu/grasscutter/utils/Crypto.java +++ b/src/main/java/emu/grasscutter/utils/Crypto.java @@ -7,6 +7,8 @@ import emu.grasscutter.Grasscutter; import emu.grasscutter.net.proto.GetPlayerTokenRspOuterClass.GetPlayerTokenRsp; import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp; +import static emu.grasscutter.Configuration.*; + public final class Crypto { private static final SecureRandom secureRandom = new SecureRandom(); public static final long ENCRYPT_SEED = Long.parseUnsignedLong("11468049314633205968"); @@ -16,9 +18,9 @@ public final class Crypto { public static byte[] ENCRYPT_KEY; public static void loadKeys() { - DISPATCH_KEY = FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "dispatchKey.bin"); - ENCRYPT_KEY = FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "secretKey.bin"); - ENCRYPT_SEED_BUFFER = FileUtils.read(Grasscutter.getConfig().KEY_FOLDER + "secretKeyBuffer.bin"); + DISPATCH_KEY = FileUtils.read(KEYS_FOLDER + "/dispatchKey.bin"); + ENCRYPT_KEY = FileUtils.read(KEYS_FOLDER + "/secretKey.bin"); + ENCRYPT_SEED_BUFFER = FileUtils.read(KEYS_FOLDER + "/secretKeyBuffer.bin"); } public static void xor(byte[] packet, byte[] key) { @@ -34,7 +36,7 @@ public final class Crypto { public static void extractSecretKeyBuffer(byte[] data) { try { GetPlayerTokenRsp p = GetPlayerTokenRsp.parseFrom(data); - FileUtils.write(Grasscutter.getConfig().KEY_FOLDER + "secretKeyBuffer.bin", p.getSecretKeyBytes().toByteArray()); + FileUtils.write(KEYS_FOLDER + "/secretKeyBuffer.bin", p.getSecretKeyBytes().toByteArray()); Grasscutter.getLogger().info("Secret Key: " + p.getSecretKey()); } catch (Exception e) { Grasscutter.getLogger().error("Crypto error.", e); @@ -44,7 +46,7 @@ public final class Crypto { public static void extractDispatchSeed(String data) { try { QueryCurrRegionHttpRsp p = QueryCurrRegionHttpRsp.parseFrom(Base64.getDecoder().decode(data)); - FileUtils.write(Grasscutter.getConfig().KEY_FOLDER + "dispatchSeed.bin", p.getRegionInfo().getSecretKey().toByteArray()); + FileUtils.write(KEYS_FOLDER + "/dispatchSeed.bin", p.getRegionInfo().getSecretKey().toByteArray()); } catch (Exception e) { Grasscutter.getLogger().error("Crypto error.", e); } diff --git a/src/main/java/emu/grasscutter/utils/Language.java b/src/main/java/emu/grasscutter/utils/Language.java index 7c3426384..4698606d7 100644 --- a/src/main/java/emu/grasscutter/utils/Language.java +++ b/src/main/java/emu/grasscutter/utils/Language.java @@ -10,6 +10,8 @@ import java.io.InputStream; import java.util.concurrent.ConcurrentHashMap; import java.util.Map; +import static emu.grasscutter.Configuration.*; + public final class Language { private static final Map cachedLanguages = new ConcurrentHashMap<>(); @@ -27,8 +29,8 @@ public final class Language { return cachedLanguages.get(langCode); } - var fallbackLanguageCode = Utils.getLanguageCode(Grasscutter.getConfig().DefaultLanguage); - var description = getLanguageFileStreamDescripter(langCode, fallbackLanguageCode); + var fallbackLanguageCode = Utils.getLanguageCode(FALLBACK_LANGUAGE); + var description = getLanguageFileDescription(langCode, fallbackLanguageCode); var actualLanguageCode = description.getLanguageCode(); Language languageInst; @@ -111,7 +113,7 @@ public final class Language { * @param languageCode The name of the language code. * @param fallbackLanguageCode The name of the fallback language code. */ - private static LanguageStreamDescription getLanguageFileStreamDescripter(String languageCode, String fallbackLanguageCode) { + private static LanguageStreamDescription getLanguageFileDescription(String languageCode, String fallbackLanguageCode) { var fileName = languageCode + ".json"; var fallback = fallbackLanguageCode + ".json"; diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index 764993255..648419b51 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -12,6 +12,7 @@ import java.util.Random; import java.util.Locale; import emu.grasscutter.Config; +import emu.grasscutter.Configuration; import emu.grasscutter.Grasscutter; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; @@ -175,12 +176,12 @@ public final class Utils { * Checks for required files and folders before startup. */ public static void startupCheck() { - Config config = Grasscutter.getConfig(); + Configuration config = Grasscutter.getConfig(); Logger logger = Grasscutter.getLogger(); boolean exit = false; - String resourcesFolder = config.RESOURCE_FOLDER; - String dataFolder = config.DATA_FOLDER; + String resourcesFolder = config.folderStructure.resources; + String dataFolder = config.folderStructure.data; // Check for resources folder. if(!fileExists(resourcesFolder)) { From bd9e207a8c1b43e4f14b33b2611bb68bfe96cea7 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 11 May 2022 00:46:49 -0400 Subject: [PATCH 078/312] Remove old config & migrate legacy configs --- src/main/java/emu/grasscutter/Configuration.java | 13 ++++++++++++- src/main/java/emu/grasscutter/Grasscutter.java | 4 +++- src/main/java/emu/grasscutter/utils/Utils.java | 1 - 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/java/emu/grasscutter/Configuration.java b/src/main/java/emu/grasscutter/Configuration.java index 369160f42..7f7bf16fe 100644 --- a/src/main/java/emu/grasscutter/Configuration.java +++ b/src/main/java/emu/grasscutter/Configuration.java @@ -1,8 +1,10 @@ package emu.grasscutter; +import com.google.gson.JsonObject; import emu.grasscutter.Grasscutter.*; import emu.grasscutter.game.mail.Mail.*; +import java.io.FileReader; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Locale; @@ -24,7 +26,16 @@ public final class Configuration { * Attempts to update the server's existing configuration to the latest configuration. */ public static void updateConfig() { - var existing = config.version; + try { // Check if the server is using a legacy config. + JsonObject configObject = Grasscutter.getGsonFactory() + .fromJson(new FileReader(Grasscutter.configFile), JsonObject.class); + if(!configObject.has("version")) { + Grasscutter.getLogger().info("Updating legacy configuration..."); + Grasscutter.saveConfig(null); + } + } catch (Exception ignored) { } + + var existing = config.version; var latest = version(); if(existing == latest) diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 07cc1f240..6ba8bd986 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -43,7 +43,7 @@ public final class Grasscutter { private static Language language; private static final Gson gson = new GsonBuilder().setPrettyPrinting().create(); - private static final File configFile = new File("./config.json"); + public static final File configFile = new File("./config.json"); private static int day; // Current day of week. @@ -60,6 +60,8 @@ public final class Grasscutter { // Load server configuration. config = Grasscutter.loadConfig(); + // Attempt to update configuration. + Configuration.updateConfig(); // Load translation files. Grasscutter.loadLanguage(); diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index 648419b51..6b8365170 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -11,7 +11,6 @@ import java.util.Map; import java.util.Random; import java.util.Locale; -import emu.grasscutter.Config; import emu.grasscutter.Configuration; import emu.grasscutter.Grasscutter; import io.netty.buffer.ByteBuf; From 0b3e2b3017417f9dec2a02a61169adb84c43fc22 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 11 May 2022 00:48:40 -0400 Subject: [PATCH 079/312] Remove config file --- src/main/java/emu/grasscutter/Config.java | 110 ---------------------- 1 file changed, 110 deletions(-) delete mode 100644 src/main/java/emu/grasscutter/Config.java diff --git a/src/main/java/emu/grasscutter/Config.java b/src/main/java/emu/grasscutter/Config.java deleted file mode 100644 index 4ec16e0d1..000000000 --- a/src/main/java/emu/grasscutter/Config.java +++ /dev/null @@ -1,110 +0,0 @@ -package emu.grasscutter; - -import java.util.Locale; -import emu.grasscutter.Grasscutter.ServerDebugMode; -import emu.grasscutter.Grasscutter.ServerRunMode; -import emu.grasscutter.game.mail.Mail; - -public final class Config { - public String DatabaseUrl = "mongodb://localhost:27017"; - public String DatabaseCollection = "grasscutter"; - - public String RESOURCE_FOLDER = "./resources/"; - public String DATA_FOLDER = "./data/"; - public String PACKETS_FOLDER = "./packets/"; - public String DUMPS_FOLDER = "./dumps/"; - public String KEY_FOLDER = "./keys/"; - public String SCRIPTS_FOLDER = "./resources/Scripts/"; - public String PLUGINS_FOLDER = "./plugins/"; - - public ServerDebugMode DebugMode = ServerDebugMode.NONE; // ALL, MISSING, NONE - public ServerRunMode RunMode = ServerRunMode.HYBRID; // HYBRID, DISPATCH_ONLY, GAME_ONLY - public GameServerOptions GameServer = new GameServerOptions(); - public DispatchServerOptions DispatchServer = new DispatchServerOptions(); - public Locale LocaleLanguage = Locale.getDefault(); - public Locale DefaultLanguage = Locale.US; - - public Boolean OpenStamina = true; - public GameServerOptions getGameServerOptions() { - return GameServer; - } - - public DispatchServerOptions getDispatchOptions() { return DispatchServer; } - - public static class DispatchServerOptions { - public String Ip = "0.0.0.0"; - public String PublicIp = "127.0.0.1"; - public int Port = 443; - public int PublicPort = 0; - public String KeystorePath = "./keystore.p12"; - public String KeystorePassword = "123456"; - public Boolean UseSSL = true; - public Boolean FrontHTTPS = true; - public Boolean CORS = false; - public String[] CORSAllowedOrigins = new String[] { "*" }; - - public boolean AutomaticallyCreateAccounts = false; - public String[] defaultPermissions = new String[] { "" }; - - public RegionInfo[] GameServers = {}; - - public RegionInfo[] getGameServers() { - return GameServers; - } - - public static class RegionInfo { - public String Name = "os_usa"; - public String Title = "Test"; - public String Ip = "127.0.0.1"; - public int Port = 22102; - } - } - - public static class GameServerOptions { - public String Name = "Test"; - public String Ip = "0.0.0.0"; - public String PublicIp = "127.0.0.1"; - public int Port = 22102; - public int PublicPort = 0; - - public String DispatchServerDatabaseUrl = "mongodb://localhost:27017"; - public String DispatchServerDatabaseCollection = "grasscutter"; - - public int InventoryLimitWeapon = 2000; - public int InventoryLimitRelic = 2000; - public int InventoryLimitMaterial = 2000; - public int InventoryLimitFurniture = 2000; - public int InventoryLimitAll = 30000; - public int MaxAvatarsInTeam = 4; - public int MaxAvatarsInTeamMultiplayer = 4; - public int MaxEntityLimit = 1000; // Max entity limit per world. // TODO: Enforce later. - public boolean WatchGacha = false; - public String ServerNickname = "Server"; - public int ServerAvatarId = 10000007; - public int ServerNameCardId = 210001; - public int ServerLevel = 1; - public int ServerWorldLevel = 1; - public String ServerSignature = "Server Signature"; - public int[] WelcomeEmotes = {2007, 1002, 4010}; - public String WelcomeMotd = "Welcome to Grasscutter emu"; - public String WelcomeMailTitle = "Welcome to Grasscutter!"; - public String WelcomeMailSender = "Lawnmower"; - public String WelcomeMailContent = "Hi there!\r\nFirst of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r\n\r\nCheck out our:\r\n"; - public Mail.MailItem[] WelcomeMailItems = { - new Mail.MailItem(13509, 1, 1), - new Mail.MailItem(201, 10000, 1), - }; - - public boolean EnableOfficialShop = true; - - public GameRates Game = new GameRates(); - - public GameRates getGameRates() { return Game; } - - public static class GameRates { - public float ADVENTURE_EXP_RATE = 1.0f; - public float MORA_RATE = 1.0f; - public float DOMAIN_DROP_RATE = 1.0f; - } - } -} From 812f4b372c4223c225710d2448c32484447adf21 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 11 May 2022 01:23:18 -0400 Subject: [PATCH 080/312] Fix `ClassDefNotFound` & `NullPointerException` errors --- .../java/emu/grasscutter/Configuration.java | 227 +---------------- .../java/emu/grasscutter/Grasscutter.java | 45 ++-- .../command/commands/ReloadCommand.java | 2 +- .../grasscutter/utils/ConfigContainer.java | 229 ++++++++++++++++++ .../java/emu/grasscutter/utils/Utils.java | 3 +- 5 files changed, 249 insertions(+), 257 deletions(-) create mode 100644 src/main/java/emu/grasscutter/utils/ConfigContainer.java diff --git a/src/main/java/emu/grasscutter/Configuration.java b/src/main/java/emu/grasscutter/Configuration.java index 7f7bf16fe..7adc334c1 100644 --- a/src/main/java/emu/grasscutter/Configuration.java +++ b/src/main/java/emu/grasscutter/Configuration.java @@ -1,12 +1,7 @@ package emu.grasscutter; -import com.google.gson.JsonObject; -import emu.grasscutter.Grasscutter.*; -import emu.grasscutter.game.mail.Mail.*; +import emu.grasscutter.utils.ConfigContainer; -import java.io.FileReader; -import java.lang.reflect.Field; -import java.util.Arrays; import java.util.Locale; import static emu.grasscutter.Grasscutter.config; @@ -17,56 +12,14 @@ import static emu.grasscutter.Grasscutter.config; * Use `import static emu.grasscutter.Configuration.*;` * to import all configuration constants. */ -public final class Configuration { - private static int version() { - return 1; - } - - /** - * Attempts to update the server's existing configuration to the latest configuration. - */ - public static void updateConfig() { - try { // Check if the server is using a legacy config. - JsonObject configObject = Grasscutter.getGsonFactory() - .fromJson(new FileReader(Grasscutter.configFile), JsonObject.class); - if(!configObject.has("version")) { - Grasscutter.getLogger().info("Updating legacy configuration..."); - Grasscutter.saveConfig(null); - } - } catch (Exception ignored) { } - - var existing = config.version; - var latest = version(); - - if(existing == latest) - return; - - // Create a new configuration instance. - Configuration updated = new Configuration(); - // Update all configuration fields. - Field[] fields = Configuration.class.getDeclaredFields(); - Arrays.stream(fields).forEach(field -> { - try { - field.set(updated, field.get(config)); - } catch (Exception exception) { - Grasscutter.getLogger().error("Failed to update a configuration field.", exception); - } - }); - - try { // Save configuration & reload. - Grasscutter.saveConfig(updated); - Grasscutter.reloadConfig(); - } catch (Exception exception) { - Grasscutter.getLogger().warn("Failed to inject the updated configuration.", exception); - } - } +public final class Configuration extends ConfigContainer { /* * Constants */ // 'c' is short for 'config' and makes code look 'cleaner'. - public static final Configuration c = config; + public static final ConfigContainer c = config; public static final Locale LANGUAGE = config.language.language; public static final Locale FALLBACK_LANGUAGE = config.language.fallback; @@ -135,178 +88,4 @@ public final class Configuration { public static int lr(int left, int right) { return left == 0 ? right : left; } - - /* - * Configuration data. - */ - - public Structure folderStructure; - public Database databaseInfo; - public Language language; - public Account account; - public Server server; - - // DO NOT. TOUCH. THE VERSION NUMBER. - public int version = version(); - - /* Option containers. */ - - public static class Database { - public String connectionUri = "mongodb://localhost:27017"; - public String collection = "grasscutter"; - } - - public static class Structure { - public String resources = "./resources/"; - public String data = "./data/"; - public String packets = "./packets/"; - public String keys = "./keys/"; - public String scripts = "./resources/scripts/"; - public String plugins = "./plugins/"; - - // UNUSED (potentially added later?) - // public String dumps = "./dumps/"; - } - - public static class Server { - public ServerDebugMode debugLevel = ServerDebugMode.NONE; - public ServerRunMode runMode = ServerRunMode.HYBRID; - - public Dispatch dispatch = new Dispatch(); - public Game game = new Game(); - } - - public static class Language { - public Locale language = Locale.getDefault(); - public Locale fallback = Locale.US; - } - - public static class Account { - public boolean autoCreate = false; - public String[] defaultPermissions = {}; - } - - /* Server options. */ - - public static class Dispatch { - public String bindAddress = "0.0.0.0"; - /* This is the address used in URLs. */ - public String accessAddress = "127.0.0.1"; - - public int bindPort = 443; - /* This is the port used in URLs. */ - public int accessPort = 443; - - public Encryption encryption = new Encryption(); - public Policies policies = new Policies(); - public Region[] regions = {}; - - public String defaultName = "Grasscutter"; - } - - public static class Game { - public String bindAddress = "0.0.0.0"; - /* This is the address used in the default region. */ - public String accessAddress = "127.0.0.1"; - - public int bindPort = 443; - /* This is the port used in the default region. */ - public int accessPort = 443; - - public GameOptions gameOptions = new GameOptions(); - public JoinOptions joinOptions = new JoinOptions(); - public ConsoleAccount serverAccount = new ConsoleAccount(); - } - - /* Data containers. */ - - public static class Encryption { - public boolean useEncryption = true; - /* Should 'https' be appended to URLs? */ - public boolean useInRouting = true; - public String keystore = "./keystore.p12"; - public String keystorePassword = "123456"; - } - - public static class Policies { - public CORS cors = new CORS(); - - public static class CORS { - public boolean enabled = false; - public String[] allowedOrigins = new String[]{"*"}; - } - } - - public static class GameOptions { - public InventoryLimits inventoryLimits = new InventoryLimits(); - public AvatarLimits avatarLimits = new AvatarLimits(); - public int worldEntityLimit = 1000; // Unenforced. TODO: Implement. - - public boolean watchGachaConfig = false; - public boolean enableShopItems = true; - public boolean staminaUsage = true; - public Rates rates = new Rates(); - - public Database databaseInfo = new Database(); - - public static class InventoryLimits { - public int weapons = 2000; - public int relics = 2000; - public int materials = 2000; - public int furniture = 2000; - public int all = 30000; - } - - public static class AvatarLimits { - public int singlePlayerTeam = 4; - public int multiplayerTeam = 4; - } - - public static class Rates { - public float adventureExp = 1.0f; - public float mora = 1.0f; - public float leyLines = 1.0f; - } - } - - public static class JoinOptions { - public int[] welcomeEmotes = {2007, 1002, 4010}; - public String welcomeMessage = "Welcome to a Grasscutter server."; - public Mail welcomeMail = new Mail(); - - public static class Mail { - public String title = "Welcome to Grasscutter!"; - public String content = """ - Hi there!\r - First of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r - \r - Check out our:\r - - """; - public String sender = "Lawnmower"; - public MailItem[] items = { - new MailItem(13509, 1, 1), - new MailItem(201, 99999, 1) - }; - } - } - - public static class ConsoleAccount { - public int avatarId = 10000007; - public int nameCardId = 210001; - public int adventureRank = 1; - public int worldLevel = 0; - - public String nickName = "Server"; - public String signature = "Welcome to Grasscutter!"; - } - - /* Objects. */ - - public static class Region { - public String Name = "os_usa"; - public String Title = "Grasscutter"; - public String Ip = "127.0.0.1"; - public int Port = 22102; - } } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 6ba8bd986..73e761e6e 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -1,14 +1,13 @@ package emu.grasscutter; import java.io.*; -import java.lang.reflect.Field; -import java.util.Arrays; import java.util.Calendar; import emu.grasscutter.command.CommandMap; import emu.grasscutter.plugin.PluginManager; import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.scripts.ScriptLoader; +import emu.grasscutter.utils.ConfigContainer; import emu.grasscutter.utils.Utils; import org.jline.reader.EndOfFileException; import org.jline.reader.LineReader; @@ -52,16 +51,16 @@ public final class Grasscutter { private static PluginManager pluginManager; public static final Reflections reflector = new Reflections("emu.grasscutter"); - public static final Configuration config; + public static ConfigContainer config; static { // Declare logback configuration. System.setProperty("logback.configurationFile", "src/main/resources/logback.xml"); // Load server configuration. - config = Grasscutter.loadConfig(); + Grasscutter.loadConfig(); // Attempt to update configuration. - Configuration.updateConfig(); + ConfigContainer.updateConfig(); // Load translation files. Grasscutter.loadLanguage(); @@ -144,34 +143,20 @@ public final class Grasscutter { /** * Attempts to load the configuration from a file. - * @return The config from the file, or a new instance. */ - public static Configuration loadConfig() { + public static void loadConfig() { try (FileReader file = new FileReader(configFile)) { - return gson.fromJson(file, Configuration.class); - } catch (Exception e) { + config = gson.fromJson(file, ConfigContainer.class); + } catch (Exception exception) { Grasscutter.saveConfig(null); - return new Configuration(); + config = new ConfigContainer(); + } catch (Error error) { + // Occurred probably from an outdated config file. + Grasscutter.saveConfig(null); + config = new ConfigContainer(); } } - /** - * Attempts to reload the configuration from the file. - * Uses reflection to **replace** the fields in the config. - */ - public static void reloadConfig() { - Configuration fileConfig = Grasscutter.loadConfig(); - - Field[] fields = Configuration.class.getDeclaredFields(); - Arrays.stream(fields).forEach(field -> { - try { - field.set(config, field.get(fileConfig)); - } catch (Exception exception) { - Grasscutter.getLogger().error("Failed to update a configuration field.", exception); - } - }); - } - public static void loadLanguage() { var locale = config.language.language; language = Language.getLanguage(Utils.getLanguageCode(locale)); @@ -181,8 +166,8 @@ public final class Grasscutter { * Saves the provided server configuration. * @param config The configuration to save, or null for a new one. */ - public static void saveConfig(@Nullable Configuration config) { - if(config == null) config = new Configuration(); + public static void saveConfig(@Nullable ConfigContainer config) { + if(config == null) config = new ConfigContainer(); try (FileWriter file = new FileWriter(configFile)) { file.write(gson.toJson(config)); @@ -231,7 +216,7 @@ public final class Grasscutter { } } - public static Configuration getConfig() { + public static ConfigContainer getConfig() { return config; } diff --git a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java index 40b8994ec..9414a89c4 100644 --- a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java @@ -16,7 +16,7 @@ public final class ReloadCommand implements CommandHandler { public void execute(Player sender, Player targetPlayer, List args) { CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_start")); - Grasscutter.reloadConfig(); + Grasscutter.loadConfig(); Grasscutter.loadLanguage(); Grasscutter.getGameServer().getGachaManager().load(); Grasscutter.getGameServer().getDropManager().load(); diff --git a/src/main/java/emu/grasscutter/utils/ConfigContainer.java b/src/main/java/emu/grasscutter/utils/ConfigContainer.java new file mode 100644 index 000000000..a848dff39 --- /dev/null +++ b/src/main/java/emu/grasscutter/utils/ConfigContainer.java @@ -0,0 +1,229 @@ +package emu.grasscutter.utils; + +import com.google.gson.JsonObject; +import emu.grasscutter.Grasscutter; + +import java.io.FileReader; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Locale; + +import static emu.grasscutter.Grasscutter.config; + +/** + * *when your JVM fails* + */ +public class ConfigContainer { + private static int version() { + return 1; + } + + /** + * Attempts to update the server's existing configuration to the latest + */ + public static void updateConfig() { + try { // Check if the server is using a legacy config. + JsonObject configObject = Grasscutter.getGsonFactory() + .fromJson(new FileReader(Grasscutter.configFile), JsonObject.class); + if(!configObject.has("version")) { + Grasscutter.getLogger().info("Updating legacy .."); + Grasscutter.saveConfig(null); + } + } catch (Exception ignored) { } + + var existing = config.version; + var latest = version(); + + if(existing == latest) + return; + + // Create a new configuration instance. + ConfigContainer updated = new ConfigContainer(); + // Update all configuration fields. + Field[] fields = ConfigContainer.class.getDeclaredFields(); + Arrays.stream(fields).forEach(field -> { + try { + field.set(updated, field.get(config)); + } catch (Exception exception) { + Grasscutter.getLogger().error("Failed to update a configuration field.", exception); + } + }); updated.version = version(); + + try { // Save configuration & reload. + Grasscutter.saveConfig(updated); + Grasscutter.loadConfig(); + } catch (Exception exception) { + Grasscutter.getLogger().warn("Failed to inject the updated ", exception); + } + } + + public Structure folderStructure = new Structure(); + public Database databaseInfo = new Database(); + public Language language = new Language(); + public Account account = new Account(); + public Server server = new Server(); + + // DO NOT. TOUCH. THE VERSION NUMBER. + public int version = version(); + + /* Option containers. */ + + public static class Database { + public String connectionUri = "mongodb://localhost:27017"; + public String collection = "grasscutter"; + } + + public static class Structure { + public String resources = "./resources/"; + public String data = "./data/"; + public String packets = "./packets/"; + public String keys = "./keys/"; + public String scripts = "./resources/scripts/"; + public String plugins = "./plugins/"; + + // UNUSED (potentially added later?) + // public String dumps = "./dumps/"; + } + + public static class Server { + public Grasscutter.ServerDebugMode debugLevel = Grasscutter.ServerDebugMode.NONE; + public Grasscutter.ServerRunMode runMode = Grasscutter.ServerRunMode.HYBRID; + + public Dispatch dispatch = new Dispatch(); + public Game game = new Game(); + } + + public static class Language { + public Locale language = Locale.getDefault(); + public Locale fallback = Locale.US; + } + + public static class Account { + public boolean autoCreate = false; + public String[] defaultPermissions = {}; + } + + /* Server options. */ + + public static class Dispatch { + public String bindAddress = "0.0.0.0"; + /* This is the address used in URLs. */ + public String accessAddress = "127.0.0.1"; + + public int bindPort = 443; + /* This is the port used in URLs. */ + public int accessPort = 443; + + public Encryption encryption = new Encryption(); + public Policies policies = new Policies(); + public Region[] regions = {}; + + public String defaultName = "Grasscutter"; + } + + public static class Game { + public String bindAddress = "0.0.0.0"; + /* This is the address used in the default region. */ + public String accessAddress = "127.0.0.1"; + + public int bindPort = 443; + /* This is the port used in the default region. */ + public int accessPort = 443; + + public GameOptions gameOptions = new GameOptions(); + public JoinOptions joinOptions = new JoinOptions(); + public ConsoleAccount serverAccount = new ConsoleAccount(); + } + + /* Data containers. */ + + public static class Encryption { + public boolean useEncryption = true; + /* Should 'https' be appended to URLs? */ + public boolean useInRouting = true; + public String keystore = "./keystore.p12"; + public String keystorePassword = "123456"; + } + + public static class Policies { + public Policies.CORS cors = new Policies.CORS(); + + public static class CORS { + public boolean enabled = false; + public String[] allowedOrigins = new String[]{"*"}; + } + } + + public static class GameOptions { + public GameOptions.InventoryLimits inventoryLimits = new GameOptions.InventoryLimits(); + public GameOptions.AvatarLimits avatarLimits = new GameOptions.AvatarLimits(); + public int worldEntityLimit = 1000; // Unenforced. TODO: Implement. + + public boolean watchGachaConfig = false; + public boolean enableShopItems = true; + public boolean staminaUsage = true; + public GameOptions.Rates rates = new GameOptions.Rates(); + + public Database databaseInfo = new Database(); + + public static class InventoryLimits { + public int weapons = 2000; + public int relics = 2000; + public int materials = 2000; + public int furniture = 2000; + public int all = 30000; + } + + public static class AvatarLimits { + public int singlePlayerTeam = 4; + public int multiplayerTeam = 4; + } + + public static class Rates { + public float adventureExp = 1.0f; + public float mora = 1.0f; + public float leyLines = 1.0f; + } + } + + public static class JoinOptions { + public int[] welcomeEmotes = {2007, 1002, 4010}; + public String welcomeMessage = "Welcome to a Grasscutter server."; + public JoinOptions.Mail welcomeMail = new JoinOptions.Mail(); + + public static class Mail { + public String title = "Welcome to Grasscutter!"; + public String content = """ + Hi there!\r + First of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you! \r + \r + Check out our:\r + + """; + public String sender = "Lawnmower"; + public emu.grasscutter.game.mail.Mail.MailItem[] items = { + new emu.grasscutter.game.mail.Mail.MailItem(13509, 1, 1), + new emu.grasscutter.game.mail.Mail.MailItem(201, 99999, 1) + }; + } + } + + public static class ConsoleAccount { + public int avatarId = 10000007; + public int nameCardId = 210001; + public int adventureRank = 1; + public int worldLevel = 0; + + public String nickName = "Server"; + public String signature = "Welcome to Grasscutter!"; + } + + /* Objects. */ + + public static class Region { + public String Name = "os_usa"; + public String Title = "Grasscutter"; + public String Ip = "127.0.0.1"; + public int Port = 22102; + } +} diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index 6b8365170..4af62bfb4 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -11,7 +11,6 @@ import java.util.Map; import java.util.Random; import java.util.Locale; -import emu.grasscutter.Configuration; import emu.grasscutter.Grasscutter; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; @@ -175,7 +174,7 @@ public final class Utils { * Checks for required files and folders before startup. */ public static void startupCheck() { - Configuration config = Grasscutter.getConfig(); + ConfigContainer config = Grasscutter.getConfig(); Logger logger = Grasscutter.getLogger(); boolean exit = false; From 37e1ffed286fe3d3c432e4869e1c524d4d6b2989 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Wed, 11 May 2022 00:44:22 -0700 Subject: [PATCH 081/312] Fix incorrect ascension level in givechar command --- .../java/emu/grasscutter/command/commands/GiveCharCommand.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java index 2917c64c4..040d07d59 100644 --- a/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/GiveCharCommand.java @@ -63,9 +63,10 @@ public final class GiveCharCommand implements CommandHandler { // Calculate ascension level. int ascension; if (level <= 40) { - ascension = (int) Math.ceil(level / 20f); + ascension = (int) Math.ceil(level / 20f) - 1; } else { ascension = (int) Math.ceil(level / 10f) - 3; + ascension = Math.min(ascension, 6); } Avatar avatar = new Avatar(avatarId); From 8ad6bda3b20eee4c48530e99a34f09541aaf635f Mon Sep 17 00:00:00 2001 From: mingjun97 Date: Wed, 11 May 2022 00:16:09 -0700 Subject: [PATCH 082/312] Bug fixes. * Fix default port for the game server * Fix the returning region info --- .../java/emu/grasscutter/server/dispatch/DispatchServer.java | 4 ++-- src/main/java/emu/grasscutter/utils/ConfigContainer.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index 3ae4ea08a..2ff35a3e2 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -125,8 +125,8 @@ public final class DispatchServer { servers.add(server); RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder() - .setGateserverIp(lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress)) - .setGateserverPort(lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort)) + .setGateserverIp(lr(GAME_INFO.accessAddress, GAME_INFO.bindAddress)) + .setGateserverPort(lr(GAME_INFO.accessPort, GAME_INFO.bindPort)) .setSecretKey(ByteString.copyFrom(FileUtils.read(KEYS_FOLDER + "/dispatchSeed.bin"))) .build(); diff --git a/src/main/java/emu/grasscutter/utils/ConfigContainer.java b/src/main/java/emu/grasscutter/utils/ConfigContainer.java index a848dff39..0a453191c 100644 --- a/src/main/java/emu/grasscutter/utils/ConfigContainer.java +++ b/src/main/java/emu/grasscutter/utils/ConfigContainer.java @@ -126,9 +126,9 @@ public class ConfigContainer { /* This is the address used in the default region. */ public String accessAddress = "127.0.0.1"; - public int bindPort = 443; + public int bindPort = 22102; /* This is the port used in the default region. */ - public int accessPort = 443; + public int accessPort = 22102; public GameOptions gameOptions = new GameOptions(); public JoinOptions joinOptions = new JoinOptions(); From 04f6e7344772e0bf1b50dd4fb037cfcd60000463 Mon Sep 17 00:00:00 2001 From: Secretboy-SMR Date: Wed, 11 May 2022 15:27:05 +0800 Subject: [PATCH 083/312] fixed language not found prompt --- .../emu/grasscutter/command/commands/LanguageCommand.java | 7 +++++++ src/main/java/emu/grasscutter/utils/Language.java | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java b/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java index 5966c6167..023b6a088 100644 --- a/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java @@ -44,6 +44,13 @@ public final class LanguageCommand implements CommandHandler { actualLangCode = languageInst.getLanguageCode(); Grasscutter.setLanguage(languageInst); } + + if (!langCode.equals(actualLangCode)) { + // I think there is no necessary to register this in language files + // since this will always be english + CommandHandler.sendMessage(sender, "currently, server does not have that language: " + langCode); + } + CommandHandler.sendMessage(sender, translate(sender, "commands.language.language_changed", actualLangCode)); } diff --git a/src/main/java/emu/grasscutter/utils/Language.java b/src/main/java/emu/grasscutter/utils/Language.java index 4698606d7..094f7465f 100644 --- a/src/main/java/emu/grasscutter/utils/Language.java +++ b/src/main/java/emu/grasscutter/utils/Language.java @@ -125,23 +125,23 @@ public final class Language { InputStream file = Grasscutter.class.getResourceAsStream("/languages/" + fileName); if (file == null) { // Provided fallback language. + Grasscutter.getLogger().warn("Failed to load language file: " + fileName + ", falling back to: " + fallback); actualLanguageCode = fallbackLanguageCode; if (cachedLanguages.containsKey(actualLanguageCode)) { return new LanguageStreamDescription(actualLanguageCode, null); } file = Grasscutter.class.getResourceAsStream("/languages/" + fallback); - Grasscutter.getLogger().warn("Failed to load language file: " + fileName + ", falling back to: " + fallback); } if(file == null) { // Fallback the fallback language. + Grasscutter.getLogger().warn("Failed to load language file: " + fallback + ", falling back to: en-US.json"); actualLanguageCode = "en-US"; if (cachedLanguages.containsKey(actualLanguageCode)) { return new LanguageStreamDescription(actualLanguageCode, null); } file = Grasscutter.class.getResourceAsStream("/languages/en-US.json"); - Grasscutter.getLogger().warn("Failed to load language file: " + fallback + ", falling back to: en-US.json"); } if(file == null) From 7e3bfed00343381b23094e4a7f0585e280a71ada Mon Sep 17 00:00:00 2001 From: coooookies <1164557342@qq.com> Date: Wed, 11 May 2022 16:54:31 +0800 Subject: [PATCH 084/312] Show server status to three-party game launcher --- .../grasscutter/server/dispatch/DispatchServer.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index 2ff35a3e2..df1ce666d 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -4,6 +4,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.protobuf.ByteString; +import emu.grasscutter.GameConstants; import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter.ServerDebugMode; import emu.grasscutter.Grasscutter.ServerRunMode; @@ -258,6 +259,15 @@ public final class DispatchServer { httpServer.post("/authentication/register", (req, res) -> this.getAuthHandler().handleRegister(req, res)); httpServer.post("/authentication/change_password", (req, res) -> this.getAuthHandler().handleChangePassword(req, res)); + // Server Status + httpServer.get("/status/server", (req, res) -> { + + int playerCount = Grasscutter.getGameServer().getPlayers().size(); + String version = GameConstants.VERSION; + + res.send("{\"retcode\":0,\"status\":{\"playerCount\":" + playerCount + ",\"version\":\"" + version + "\"}}"); + }); + // Dispatch httpServer.get("/query_region_list", (req, res) -> { // Log From e3fd2eaa1770dff595eae315291519395010b42f Mon Sep 17 00:00:00 2001 From: Secretboy-SMR Date: Wed, 11 May 2022 18:06:37 +0800 Subject: [PATCH 085/312] Removed invalid code in getLanguageFileDescription,When the language is not discovered, it will use the built-in language fallback mechanism to fall back,At the same time, we also fix the issue that using language in the server does not save the settings of the server side locale --- .../command/commands/LanguageCommand.java | 16 ++++++++-------- .../java/emu/grasscutter/utils/Language.java | 6 +----- src/main/resources/languages/en-US.json | 1 + src/main/resources/languages/zh-CN.json | 1 + 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java b/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java index 023b6a088..4783af0cc 100644 --- a/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/LanguageCommand.java @@ -31,24 +31,24 @@ public final class LanguageCommand implements CommandHandler { } String langCode = args.get(0); - String actualLangCode = null; + + var languageInst = Grasscutter.getLanguage(langCode); + var actualLangCode = languageInst.getLanguageCode(); + var locale = Locale.forLanguageTag(actualLangCode); if (sender != null) { - var locale = Locale.forLanguageTag(langCode); - actualLangCode = Utils.getLanguageCode(locale); var account = sender.getAccount(); account.setLocale(locale); account.save(); } else { - var languageInst = Grasscutter.getLanguage(langCode); - actualLangCode = languageInst.getLanguageCode(); Grasscutter.setLanguage(languageInst); + var config = Grasscutter.getConfig(); + config.language.language = locale; + Grasscutter.saveConfig(config); } if (!langCode.equals(actualLangCode)) { - // I think there is no necessary to register this in language files - // since this will always be english - CommandHandler.sendMessage(sender, "currently, server does not have that language: " + langCode); + CommandHandler.sendMessage(sender, translate(sender, "commands.language.language_not_found", langCode)); } CommandHandler.sendMessage(sender, translate(sender, "commands.language.language_changed", actualLangCode)); diff --git a/src/main/java/emu/grasscutter/utils/Language.java b/src/main/java/emu/grasscutter/utils/Language.java index 094f7465f..3789f594a 100644 --- a/src/main/java/emu/grasscutter/utils/Language.java +++ b/src/main/java/emu/grasscutter/utils/Language.java @@ -116,12 +116,8 @@ public final class Language { private static LanguageStreamDescription getLanguageFileDescription(String languageCode, String fallbackLanguageCode) { var fileName = languageCode + ".json"; var fallback = fallbackLanguageCode + ".json"; - - String actualLanguageCode = languageCode; - if (cachedLanguages.containsKey(actualLanguageCode)) { - return new LanguageStreamDescription(actualLanguageCode, null); - } + String actualLanguageCode = languageCode; InputStream file = Grasscutter.class.getResourceAsStream("/languages/" + fileName); if (file == null) { // Provided fallback language. diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 893f490af..c9c3c0c70 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -191,6 +191,7 @@ "language": { "current_language": "current language is %s", "language_changed": "language changed to %s", + "language_not_found": "currently, server does not have that language: %s", "description": "display or change current language" }, "list": { diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 8d5b35137..2f9663f4f 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -191,6 +191,7 @@ "language": { "current_language": "当前语言是: %s", "language_changed": "语言切换至: %s", + "language_not_found": "目前服务端没有这种语言: %s", "description": "显示或切换当前语言。" }, "list": { From e9d7d5d5f2a4942af7d9e92e9685a79229f9e7b8 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Wed, 11 May 2022 03:56:40 -0700 Subject: [PATCH 086/312] Implement quests --- .../command/commands/QuestCommand.java | 66 ++++++ .../java/emu/grasscutter/data/GameData.java | 6 + .../emu/grasscutter/data/ResourceLoader.java | 34 +++- .../grasscutter/data/custom/QuestConfig.java | 25 +++ .../data/custom/QuestConfigData.java | 104 ++++++++++ .../grasscutter/database/DatabaseHelper.java | 15 ++ .../grasscutter/database/DatabaseManager.java | 5 +- .../emu/grasscutter/game/player/Player.java | 38 +++- .../grasscutter/game/quest/GameMainQuest.java | 124 ++++++++++++ .../emu/grasscutter/game/quest/GameQuest.java | 188 ++++++++++++++++++ .../grasscutter/game/quest/QuestManager.java | 119 +++++++++++ .../game/quest/enums/LogicType.java | 23 +++ .../game/quest/enums/ParentQuestState.java | 18 ++ .../game/quest/enums/QuestCondType.java | 92 +++++++++ .../game/quest/enums/QuestExecType.java | 82 ++++++++ .../game/quest/enums/QuestGuideType.java | 17 ++ .../game/quest/enums/QuestShowType.java | 16 ++ .../game/quest/enums/QuestState.java | 19 ++ .../game/quest/enums/QuestType.java | 22 ++ .../game/quest/enums/ShowQuestGuideType.java | 17 ++ .../send/PacketFinishedParentQuestNotify.java | 22 ++ ...PacketFinishedParentQuestUpdateNotify.java | 19 ++ .../packet/send/PacketQuestListNotify.java | 23 +++ .../send/PacketQuestListUpdateNotify.java | 20 ++ .../send/PacketQuestProgressUpdateNotify.java | 30 +++ ...etServerCondMeetQuestListUpdateNotify.java | 37 ++++ src/main/resources/languages/en-US.json | 8 + 27 files changed, 1183 insertions(+), 6 deletions(-) create mode 100644 src/main/java/emu/grasscutter/command/commands/QuestCommand.java create mode 100644 src/main/java/emu/grasscutter/data/custom/QuestConfig.java create mode 100644 src/main/java/emu/grasscutter/data/custom/QuestConfigData.java create mode 100644 src/main/java/emu/grasscutter/game/quest/GameMainQuest.java create mode 100644 src/main/java/emu/grasscutter/game/quest/GameQuest.java create mode 100644 src/main/java/emu/grasscutter/game/quest/QuestManager.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/LogicType.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/ParentQuestState.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/QuestCondType.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/QuestExecType.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/QuestGuideType.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/QuestShowType.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/QuestState.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/QuestType.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/ShowQuestGuideType.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestUpdateNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketQuestListNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketQuestListUpdateNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketQuestProgressUpdateNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java diff --git a/src/main/java/emu/grasscutter/command/commands/QuestCommand.java b/src/main/java/emu/grasscutter/command/commands/QuestCommand.java new file mode 100644 index 000000000..70fae0120 --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/QuestCommand.java @@ -0,0 +1,66 @@ +package emu.grasscutter.command.commands; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameQuest; + +import java.util.List; + +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "quest", usage = "quest [quest id]", permission = "player.quest", permissionTargeted = "player.quest.others", description = "commands.quest.description") +public final class QuestCommand implements CommandHandler { + + @Override + public void execute(Player sender, Player targetPlayer, List args) { + if (targetPlayer == null) { + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); + return; + } + + if (args.size() != 2) { + CommandHandler.sendMessage(sender, translate(sender, "commands.quest.usage")); + return; + } + + String cmd = args.get(0).toLowerCase(); + int questId; + + try { + questId = Integer.parseInt(args.get(1)); + } catch (Exception e) { + CommandHandler.sendMessage(sender, translate(sender, "commands.quest.invalid_id")); + return; + } + + switch (cmd) { + case "add" -> { + GameQuest quest = sender.getQuestManager().addQuest(questId); + + if (quest != null) { + CommandHandler.sendMessage(sender, translate(sender, "commands.quest.added", questId)); + return; + } + + CommandHandler.sendMessage(sender, translate(sender, "commands.quest.not_found")); + } + case "finish" -> { + GameQuest quest = sender.getQuestManager().getQuestById(questId); + + if (quest == null) { + CommandHandler.sendMessage(sender, translate(sender, "commands.quest.not_found")); + return; + } + + quest.finish(); + + CommandHandler.sendMessage(sender, translate(sender, "commands.quest.finished", questId)); + } + default -> { + CommandHandler.sendMessage(sender, translate(sender, "commands.quest.usage")); + } + } + } +} diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index ac2472192..2b40818e1 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -12,6 +12,7 @@ import emu.grasscutter.data.custom.AbilityEmbryoEntry; import emu.grasscutter.data.custom.AbilityModifier; import emu.grasscutter.data.custom.AbilityModifierEntry; import emu.grasscutter.data.custom.OpenConfigEntry; +import emu.grasscutter.data.custom.QuestConfig; import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.data.def.*; import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; @@ -27,6 +28,7 @@ public class GameData { private static final Map abilityModifiers = new HashMap<>(); private static final Map openConfigEntries = new HashMap<>(); private static final Map scenePointEntries = new HashMap<>(); + private static final Int2ObjectMap questConfigs = new Int2ObjectOpenHashMap<>(); // ExcelConfigs private static final Int2ObjectMap playerLevelDataMap = new Int2ObjectOpenHashMap<>(); @@ -122,6 +124,10 @@ public class GameData { return getScenePointEntries().get(sceneId + "_" + pointId); } + public static Int2ObjectMap getQuestConfigs() { + return questConfigs; + } + public static Int2ObjectMap getAvatarDataMap() { return avatarDataMap; } diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index c2708bd63..ae73de8ff 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -24,6 +24,9 @@ import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction; import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierActionType; import emu.grasscutter.data.custom.AbilityModifierEntry; import emu.grasscutter.data.custom.OpenConfigEntry; +import emu.grasscutter.data.custom.QuestConfig; +import emu.grasscutter.data.custom.QuestConfigData; +import emu.grasscutter.data.custom.QuestConfigData.SubQuestConfigData; import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.game.world.SpawnDataEntry; import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry; @@ -57,8 +60,9 @@ public class ResourceLoader { loadResources(); // Process into depots GameDepot.load(); - // Load spawn data + // Load spawn data and quests loadSpawnData(); + loadQuests(); // Load scene points - must be done AFTER resources are loaded loadScenePoints(); // Custom - TODO move this somewhere else @@ -396,6 +400,34 @@ public class ResourceLoader { GameData.getOpenConfigEntries().put(entry.getName(), entry); } } + + private static void loadQuests() { + File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Quest/"); + + if (!folder.exists()) { + return; + } + + for (File file : folder.listFiles()) { + QuestConfigData mainQuest = null; + + try (FileReader fileReader = new FileReader(file)) { + mainQuest = Grasscutter.getGsonFactory().fromJson(fileReader, QuestConfigData.class); + } catch (Exception e) { + e.printStackTrace(); + continue; + } + + if (mainQuest.getSubQuests() != null) { + for (SubQuestConfigData subQuest : mainQuest.getSubQuests()) { + QuestConfig quest = new QuestConfig(mainQuest, subQuest); + GameData.getQuestConfigs().put(quest.getId(), quest); + } + } + } + + Grasscutter.getLogger().info("Loaded " + GameData.getQuestConfigs().size() + " Quest Configs"); + } // BinOutput configs diff --git a/src/main/java/emu/grasscutter/data/custom/QuestConfig.java b/src/main/java/emu/grasscutter/data/custom/QuestConfig.java new file mode 100644 index 000000000..8674ff7ab --- /dev/null +++ b/src/main/java/emu/grasscutter/data/custom/QuestConfig.java @@ -0,0 +1,25 @@ +package emu.grasscutter.data.custom; + +import emu.grasscutter.data.custom.QuestConfigData.SubQuestConfigData; + +public class QuestConfig { + private final QuestConfigData mainQuest; + private final SubQuestConfigData subQuest; + + public QuestConfig(QuestConfigData mainQuest, SubQuestConfigData subQuest) { + this.mainQuest = mainQuest; + this.subQuest = subQuest; + } + + public int getId() { + return subQuest.getSubId(); + } + + public QuestConfigData getMainQuest() { + return mainQuest; + } + + public SubQuestConfigData getSubQuest() { + return subQuest; + } +} diff --git a/src/main/java/emu/grasscutter/data/custom/QuestConfigData.java b/src/main/java/emu/grasscutter/data/custom/QuestConfigData.java new file mode 100644 index 000000000..4ba0ce47c --- /dev/null +++ b/src/main/java/emu/grasscutter/data/custom/QuestConfigData.java @@ -0,0 +1,104 @@ +package emu.grasscutter.data.custom; + +import emu.grasscutter.game.quest.enums.LogicType; +import emu.grasscutter.game.quest.enums.QuestCondType; +import emu.grasscutter.game.quest.enums.QuestType; + +public class QuestConfigData { + private int id; + private int series; + private QuestType type; + + private long titleTextMapHash; + private int[] suggestTrackMainQuestList; + private int[] rewardIdList; + + private SubQuestConfigData[] subQuests; + + public int getId() { + return id; + } + + public int getSeries() { + return series; + } + + public QuestType getType() { + return type; + } + + public long getTitleTextMapHash() { + return titleTextMapHash; + } + + public int[] getSuggestTrackMainQuestList() { + return suggestTrackMainQuestList; + } + + public int[] getRewardIdList() { + return rewardIdList; + } + + public SubQuestConfigData[] getSubQuests() { + return subQuests; + } + + public class SubQuestConfigData { + private int subId; + private int mainId; + + private LogicType acceptCondComb; + private QuestCondition[] acceptCond; + + private LogicType finishCondComb; + private QuestCondition[] finishCond; + + private LogicType failCondComb; + private QuestCondition[] failCond; + + public int getSubId() { + return subId; + } + + public int getMainId() { + return mainId; + } + + public LogicType getAcceptCondComb() { + return acceptCondComb; + } + + public QuestCondition[] getAcceptCond() { + return acceptCond; + } + + public LogicType getFinishCondComb() { + return finishCondComb; + } + + public QuestCondition[] getFinishCond() { + return finishCond; + } + + public LogicType getFailCondComb() { + return failCondComb; + } + + public QuestCondition[] getFailCond() { + return failCond; + } + } + + public class QuestCondition { + private QuestCondType type; + private int[] param; + + public QuestCondType getType() { + return type; + } + + public int[] getParam() { + return param; + } + } +} diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index 8f1de0bb9..fe931bdc3 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -15,6 +15,7 @@ import emu.grasscutter.game.gacha.GachaRecord; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameMainQuest; import static com.mongodb.client.model.Filters.eq; @@ -111,6 +112,8 @@ public final class DatabaseHelper { DatabaseManager.getDatabase().getCollection("gachas").deleteMany(eq("ownerId", target.getPlayerUid())); // Delete GameItem.class data DatabaseManager.getDatabase().getCollection("items").deleteMany(eq("ownerId", target.getPlayerUid())); + // Delete GameMainQuest.class data + DatabaseManager.getDatabase().getCollection("quests").deleteMany(eq("ownerUid", target.getPlayerUid())); // Delete friendships. // Here, we need to make sure to not only delete the deleted account's friendships, @@ -260,4 +263,16 @@ public final class DatabaseHelper { DeleteResult result = DatabaseManager.getDatastore().delete(mail); return result.wasAcknowledged(); } + + public static List getAllQuests(Player player) { + return DatabaseManager.getDatastore().find(GameMainQuest.class).filter(Filters.eq("ownerUid", player.getUid())).stream().toList(); + } + + public static void saveQuest(GameMainQuest quest) { + DatabaseManager.getDatastore().save(quest); + } + + public static boolean deleteQuest(GameMainQuest quest) { + return DatabaseManager.getDatastore().delete(quest).wasAcknowledged(); + } } diff --git a/src/main/java/emu/grasscutter/database/DatabaseManager.java b/src/main/java/emu/grasscutter/database/DatabaseManager.java index 90ff17238..12bb444b8 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseManager.java +++ b/src/main/java/emu/grasscutter/database/DatabaseManager.java @@ -20,6 +20,8 @@ import emu.grasscutter.game.gacha.GachaRecord; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; public final class DatabaseManager { @@ -30,7 +32,8 @@ public final class DatabaseManager { private static Datastore dispatchDatastore; private static final Class[] mappedClasses = new Class[] { - DatabaseCounter.class, Account.class, Player.class, Avatar.class, GameItem.class, Friendship.class, GachaRecord.class, Mail.class + DatabaseCounter.class, Account.class, Player.class, Avatar.class, GameItem.class, Friendship.class, + GachaRecord.class, Mail.class, GameMainQuest.class }; public static Datastore getDatastore() { diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 477f974ea..e8c4cd61b 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -29,6 +29,9 @@ import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.SceneType; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestManager; import emu.grasscutter.game.shop.ShopLimit; import emu.grasscutter.game.managers.MapMarkManager.*; import emu.grasscutter.game.tower.TowerManager; @@ -91,6 +94,7 @@ public class Player { @Transient private MailHandler mailHandler; @Transient private MessageHandler messageHandler; @Transient private AbilityManager abilityManager; + @Transient private QuestManager questManager; @Transient private SotSManager sotsManager; @@ -145,6 +149,7 @@ public class Player { this.mailHandler = new MailHandler(this); this.towerManager = new TowerManager(this); this.abilityManager = new AbilityManager(this); + this.setQuestManager(new QuestManager(this)); this.pos = new Position(); this.rotation = new Position(); this.properties = new HashMap<>(); @@ -409,6 +414,14 @@ public class Player { return towerManager; } + public QuestManager getQuestManager() { + return questManager; + } + + public void setQuestManager(QuestManager questManager) { + this.questManager = questManager; + } + public PlayerGachaInfo getGachaInfo() { return gachaInfo; } @@ -883,9 +896,7 @@ public class Player { } public void sendPacket(BasePacket packet) { - if (this.hasSentAvatarDataNotify) { - this.getSession().send(packet); - } + this.getSession().send(packet); } public OnlinePlayerInfo getOnlinePlayerInfo() { @@ -1118,7 +1129,23 @@ public class Player { this.getFriendsList().loadFromDatabase(); this.getMailHandler().loadFromDatabase(); + this.getQuestManager().loadFromDatabase(); + + // Quest - Commented out because a problem is caused if you log out while this quest is active + /* + if (getQuestManager().getMainQuestById(351) == null) { + GameQuest quest = getQuestManager().addQuest(35104); + if (quest != null) { + quest.finish(); + } + getQuestManager().addQuest(35101); + + this.setSceneId(3); + this.getPos().set(GameConstants.START_POSITION); + } + */ + // Create world World world = new World(this); world.addPlayer(this); @@ -1138,7 +1165,10 @@ public class Player { session.send(new PacketStoreWeightLimitNotify()); session.send(new PacketPlayerStoreNotify(this)); session.send(new PacketAvatarDataNotify(this)); - + session.send(new PacketFinishedParentQuestNotify(this)); + session.send(new PacketQuestListNotify(this)); + session.send(new PacketServerCondMeetQuestListUpdateNotify(this)); + getTodayMoonCard(); // The timer works at 0:0, some users log in after that, use this method to check if they have received a reward today or not. If not, send the reward. session.send(new PacketPlayerEnterSceneNotify(this)); // Enter game world diff --git a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java new file mode 100644 index 000000000..1ceda3356 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java @@ -0,0 +1,124 @@ +package emu.grasscutter.game.quest; + +import java.util.HashMap; +import java.util.Map; + +import org.bson.types.ObjectId; + +import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Id; +import dev.morphia.annotations.Indexed; +import dev.morphia.annotations.Transient; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.custom.QuestConfig; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.enums.ParentQuestState; +import emu.grasscutter.game.quest.enums.QuestState; +import emu.grasscutter.net.proto.ChildQuestOuterClass.ChildQuest; +import emu.grasscutter.net.proto.ParentQuestOuterClass.ParentQuest; +import emu.grasscutter.net.proto.QuestOuterClass.Quest; +import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify; +import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify; +import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify; +import emu.grasscutter.utils.Utils; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +@Entity(value = "quests", useDiscriminator = false) +public class GameMainQuest { + @Id private ObjectId id; + + @Indexed private int ownerUid; + @Transient private Player owner; + + private Map childQuests; + + private int parentQuestId; + private int[] questVars; + private ParentQuestState state; + private boolean isFinished; + + @Deprecated // Morphia only. Do not use. + public GameMainQuest() {} + + public GameMainQuest(Player player, int parentQuestId) { + this.owner = player; + this.ownerUid = player.getUid(); + this.parentQuestId = parentQuestId; + this.childQuests = new HashMap<>(); + this.questVars = new int[5]; + this.state = ParentQuestState.PARENT_QUEST_STATE_NONE; + } + + public int getParentQuestId() { + return parentQuestId; + } + + public int getOwnerUid() { + return ownerUid; + } + + public Player getOwner() { + return owner; + } + + public void setOwner(Player player) { + if (player.getUid() != this.getOwnerUid()) return; + this.owner = player; + } + + public Map getChildQuests() { + return childQuests; + } + + public GameQuest getChildQuestById(int id) { + return this.getChildQuests().get(id); + } + + public int[] getQuestVars() { + return questVars; + } + + public ParentQuestState getState() { + return state; + } + + public boolean isFinished() { + return isFinished; + } + + public void finish() { + this.isFinished = true; + this.state = ParentQuestState.PARENT_QUEST_STATE_FINISHED; + this.getOwner().getSession().send(new PacketFinishedParentQuestUpdateNotify(this)); + } + + public void save() { + DatabaseHelper.saveQuest(this); + } + + public ParentQuest toProto() { + ParentQuest.Builder proto = ParentQuest.newBuilder() + .setParentQuestId(getParentQuestId()) + .setIsFinished(isFinished()) + .setParentQuestState(getState().getValue()); + + for (GameQuest quest : this.getChildQuests().values()) { + ChildQuest childQuest = ChildQuest.newBuilder() + .setQuestId(quest.getQuestId()) + .setState(quest.getState().getValue()) + .build(); + + proto.addChildQuestList(childQuest); + } + + if (getQuestVars() != null) { + for (int i : getQuestVars()) { + proto.addQuestVar(i); + } + } + + return proto.build(); + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/GameQuest.java b/src/main/java/emu/grasscutter/game/quest/GameQuest.java new file mode 100644 index 000000000..53599830c --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/GameQuest.java @@ -0,0 +1,188 @@ +package emu.grasscutter.game.quest; + +import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Transient; +import emu.grasscutter.data.custom.QuestConfig; +import emu.grasscutter.data.custom.QuestConfigData.SubQuestConfigData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.enums.QuestState; +import emu.grasscutter.net.proto.QuestOuterClass.Quest; +import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify; +import emu.grasscutter.utils.Utils; + +@Entity +public class GameQuest { + @Transient private GameMainQuest mainQuest; + @Transient private QuestConfig config; + + private int questId; + private int mainQuestId; + private QuestState state; + + private int startTime; + private int acceptTime; + private int finishTime; + + private int[] finishProgressList; + private int[] failProgressList; + + @Deprecated // Morphia only. Do not use. + public GameQuest() {} + + public GameQuest(GameMainQuest mainQuest, QuestConfig config) { + this.mainQuest = mainQuest; + this.questId = config.getId(); + this.mainQuestId = config.getMainQuest().getId(); + this.config = config; + this.acceptTime = Utils.getCurrentSeconds(); + this.startTime = this.acceptTime; + this.state = QuestState.QUEST_STATE_UNFINISHED; + + if (config.getSubQuest().getFinishCond() != null) { + this.finishProgressList = new int[config.getSubQuest().getFinishCond().length]; + } + + if (config.getSubQuest().getFailCond() != null) { + this.failProgressList = new int[config.getSubQuest().getFailCond().length]; + } + + this.mainQuest.getChildQuests().put(this.questId, this); + } + + public GameMainQuest getMainQuest() { + return mainQuest; + } + + public void setMainQuest(GameMainQuest mainQuest) { + this.mainQuest = mainQuest; + } + + public Player getOwner() { + return getMainQuest().getOwner(); + } + + public int getQuestId() { + return questId; + } + + public int getMainQuestId() { + return mainQuestId; + } + + public QuestConfig getConfig() { + return config; + } + + public void setConfig(QuestConfig config) { + if (this.getQuestId() != config.getId()) return; + this.config = config; + } + + public QuestState getState() { + return state; + } + + public void setState(QuestState state) { + this.state = state; + } + + public int getStartTime() { + return startTime; + } + + public void setStartTime(int startTime) { + this.startTime = startTime; + } + + public int getAcceptTime() { + return acceptTime; + } + + public void setAcceptTime(int acceptTime) { + this.acceptTime = acceptTime; + } + + public int getFinishTime() { + return finishTime; + } + + public void setFinishTime(int finishTime) { + this.finishTime = finishTime; + } + + public int[] getFinishProgressList() { + return finishProgressList; + } + + public void setFinishProgress(int index, int value) { + finishProgressList[index] = value; + } + + public int[] getFailProgressList() { + return failProgressList; + } + + public void setFailProgress(int index, int value) { + failProgressList[index] = value; + } + + public void finish() { + this.state = QuestState.QUEST_STATE_FINISHED; + this.finishTime = Utils.getCurrentSeconds(); + + if (this.getFinishProgressList() != null) { + for (int i = 0 ; i < getFinishProgressList().length; i++) { + getFinishProgressList()[i] = 1; + } + } + + this.getOwner().getSession().send(new PacketQuestProgressUpdateNotify(this)); + + // Finish main quest if all child quests are done + this.tryFinishMainQuest(); + this.save(); + } + + public boolean tryFinishMainQuest() { + try { + SubQuestConfigData subQuestData = getConfig().getMainQuest().getSubQuests()[getConfig().getMainQuest().getSubQuests().length - 1]; + + if (subQuestData.getSubId() == this.getQuestId()) { + getMainQuest().finish(); + return true; + } + } catch (Exception e) { + + } + + return false; + } + + public void save() { + getMainQuest().save(); + } + + public Quest toProto() { + Quest.Builder proto = Quest.newBuilder() + .setQuestId(this.getQuestId()) + .setState(this.getState().getValue()) + .setParentQuestId(this.getMainQuestId()) + .setStartTime(this.getStartTime()) + .setStartGameTime(438) + .setAcceptTime(this.getAcceptTime()); + + if (this.getFinishProgressList() != null) { + for (int i : this.getFinishProgressList()) { + proto.addFinishProgressList(i); + } + } + + if (this.getFailProgressList() != null) { + for (int i : this.getFailProgressList()) { + proto.addFailProgressList(i); + } + } + + return proto.build(); + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/QuestManager.java b/src/main/java/emu/grasscutter/game/quest/QuestManager.java new file mode 100644 index 000000000..e1c26704c --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/QuestManager.java @@ -0,0 +1,119 @@ +package emu.grasscutter.game.quest; + +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.custom.QuestConfig; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.enums.QuestState; +import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify; +import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify; +import emu.grasscutter.server.packet.send.PacketServerCondMeetQuestListUpdateNotify; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public class QuestManager { + private final Player player; + private final Int2ObjectMap quests; + + public QuestManager(Player player) { + this.player = player; + this.quests = new Int2ObjectOpenHashMap<>(); + } + + public Player getPlayer() { + return player; + } + + public Int2ObjectMap getQuests() { + return quests; + } + + public GameMainQuest getMainQuestById(int mainQuestId) { + return getQuests().get(mainQuestId); + } + + public GameQuest getQuestById(int questId) { + QuestConfig questConfig = GameData.getQuestConfigs().get(questId); + if (questConfig == null) { + return null; + } + + GameMainQuest mainQuest = getQuests().get(questConfig.getMainQuest().getId()); + + if (mainQuest == null) { + return null; + } + + return mainQuest.getChildQuests().get(questId); + } + + public void forEachQuest(Consumer callback) { + for (GameMainQuest mainQuest : getQuests().values()) { + for (GameQuest quest : mainQuest.getChildQuests().values()) { + callback.accept(quest); + } + } + } + + public GameMainQuest addMainQuest(QuestConfig questConfig) { + GameMainQuest mainQuest = new GameMainQuest(getPlayer(), questConfig.getMainQuest().getId()); + getQuests().put(mainQuest.getParentQuestId(), mainQuest); + + getPlayer().sendPacket(new PacketFinishedParentQuestUpdateNotify(mainQuest)); + + return mainQuest; + } + + public GameQuest addQuest(int questId) { + QuestConfig questConfig = GameData.getQuestConfigs().get(questId); + if (questConfig == null) { + return null; + } + + // Main quest + GameMainQuest mainQuest = this.getMainQuestById(questConfig.getMainQuest().getId()); + + // Create main quest if it doesnt exist + if (mainQuest == null) { + mainQuest = addMainQuest(questConfig); + } + + // Sub quest + GameQuest quest = mainQuest.getChildQuestById(questId); + + if (quest != null) { + return null; + } + + // Create + quest = new GameQuest(mainQuest, questConfig); + + // Save main quest + mainQuest.save(); + + // Send packet + getPlayer().sendPacket(new PacketServerCondMeetQuestListUpdateNotify(quest)); + getPlayer().sendPacket(new PacketQuestListUpdateNotify(quest)); + + return quest; + } + + public void loadFromDatabase() { + List quests = DatabaseHelper.getAllQuests(getPlayer()); + + for (GameMainQuest mainQuest : quests) { + mainQuest.setOwner(this.getPlayer()); + + for (GameQuest quest : mainQuest.getChildQuests().values()) { + quest.setMainQuest(mainQuest); + quest.setConfig(GameData.getQuestConfigs().get(quest.getQuestId())); + } + + this.getQuests().put(mainQuest.getParentQuestId(), mainQuest); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/LogicType.java b/src/main/java/emu/grasscutter/game/quest/enums/LogicType.java new file mode 100644 index 000000000..608ec9c28 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/LogicType.java @@ -0,0 +1,23 @@ +package emu.grasscutter.game.quest.enums; + +public enum LogicType { + LOGIC_NONE (0), + LOGIC_AND (1), + LOGIC_OR (2), + LOGIC_NOT (3), + LOGIC_A_AND_ETCOR (4), + LOGIC_A_AND_B_AND_ETCOR (5), + LOGIC_A_OR_ETCAND (6), + LOGIC_A_OR_B_OR_ETCAND (7), + LOGIC_A_AND_B_OR_ETCAND (8); + + private final int value; + + LogicType(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/ParentQuestState.java b/src/main/java/emu/grasscutter/game/quest/enums/ParentQuestState.java new file mode 100644 index 000000000..6c7805f8d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/ParentQuestState.java @@ -0,0 +1,18 @@ +package emu.grasscutter.game.quest.enums; + +public enum ParentQuestState { + PARENT_QUEST_STATE_NONE (0), + PARENT_QUEST_STATE_FINISHED (1), + PARENT_QUEST_STATE_FAILED (2), + PARENT_QUEST_STATE_CANCELED (3); + + private final int value; + + ParentQuestState(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestCondType.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestCondType.java new file mode 100644 index 000000000..42db14f2d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestCondType.java @@ -0,0 +1,92 @@ +package emu.grasscutter.game.quest.enums; + +public enum QuestCondType { + QUEST_COND_NONE (0), + QUEST_COND_STATE_EQUAL (1), + QUEST_COND_STATE_NOT_EQUAL (2), + QUEST_COND_PACK_HAVE_ITEM (3), + QUEST_COND_AVATAR_ELEMENT_EQUAL (4), + QUEST_COND_AVATAR_ELEMENT_NOT_EQUAL (5), + QUEST_COND_AVATAR_CAN_CHANGE_ELEMENT (6), + QUEST_COND_CITY_LEVEL_EQUAL_GREATER (7), + QUEST_COND_ITEM_NUM_LESS_THAN (8), + QUEST_COND_DAILY_TASK_START (9), + QUEST_COND_OPEN_STATE_EQUAL (10), + QUEST_COND_DAILY_TASK_OPEN (11), + QUEST_COND_DAILY_TASK_REWARD_CAN_GET (12), + QUEST_COND_DAILY_TASK_REWARD_RECEIVED (13), + QUEST_COND_PLAYER_LEVEL_REWARD_CAN_GET (14), + QUEST_COND_EXPLORATION_REWARD_CAN_GET (15), + QUEST_COND_IS_WORLD_OWNER (16), + QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER (17), + QUEST_COND_SCENE_AREA_UNLOCKED (18), + QUEST_COND_ITEM_GIVING_ACTIVED (19), + QUEST_COND_ITEM_GIVING_FINISHED (20), + QUEST_COND_IS_DAYTIME (21), + QUEST_COND_CURRENT_AVATAR (22), + QUEST_COND_CURRENT_AREA (23), + QUEST_COND_QUEST_VAR_EQUAL (24), + QUEST_COND_QUEST_VAR_GREATER (25), + QUEST_COND_QUEST_VAR_LESS (26), + QUEST_COND_FORGE_HAVE_FINISH (27), + QUEST_COND_DAILY_TASK_IN_PROGRESS (28), + QUEST_COND_DAILY_TASK_FINISHED (29), + QUEST_COND_ACTIVITY_COND (30), + QUEST_COND_ACTIVITY_OPEN (31), + QUEST_COND_DAILY_TASK_VAR_GT (32), + QUEST_COND_DAILY_TASK_VAR_EQ (33), + QUEST_COND_DAILY_TASK_VAR_LT (34), + QUEST_COND_BARGAIN_ITEM_GT (35), + QUEST_COND_BARGAIN_ITEM_EQ (36), + QUEST_COND_BARGAIN_ITEM_LT (37), + QUEST_COND_COMPLETE_TALK (38), + QUEST_COND_NOT_HAVE_BLOSSOM_TALK (39), + QUEST_COND_IS_CUR_BLOSSOM_TALK (40), + QUEST_COND_QUEST_NOT_RECEIVE (41), + QUEST_COND_QUEST_SERVER_COND_VALID (42), + QUEST_COND_ACTIVITY_CLIENT_COND (43), + QUEST_COND_QUEST_GLOBAL_VAR_EQUAL (44), + QUEST_COND_QUEST_GLOBAL_VAR_GREATER (45), + QUEST_COND_QUEST_GLOBAL_VAR_LESS (46), + QUEST_COND_PERSONAL_LINE_UNLOCK (47), + QUEST_COND_CITY_REPUTATION_REQUEST (48), + QUEST_COND_MAIN_COOP_START (49), + QUEST_COND_MAIN_COOP_ENTER_SAVE_POINT (50), + QUEST_COND_CITY_REPUTATION_LEVEL (51), + QUEST_COND_CITY_REPUTATION_UNLOCK (52), + QUEST_COND_LUA_NOTIFY (53), + QUEST_COND_CUR_CLIMATE (54), + QUEST_COND_ACTIVITY_END (55), + QUEST_COND_COOP_POINT_RUNNING (56), + QUEST_COND_GADGET_TALK_STATE_EQUAL (57), + QUEST_COND_AVATAR_FETTER_GT (58), + QUEST_COND_AVATAR_FETTER_EQ (59), + QUEST_COND_AVATAR_FETTER_LT (60), + QUEST_COND_NEW_HOMEWORLD_MOUDLE_UNLOCK (61), + QUEST_COND_NEW_HOMEWORLD_LEVEL_REWARD (62), + QUEST_COND_NEW_HOMEWORLD_MAKE_FINISH (63), + QUEST_COND_HOMEWORLD_NPC_EVENT (64), + QUEST_COND_TIME_VAR_GT_EQ (65), + QUEST_COND_TIME_VAR_PASS_DAY (66), + QUEST_COND_HOMEWORLD_NPC_NEW_TALK (67), + QUEST_COND_PLAYER_CHOOSE_MALE (68), + QUEST_COND_HISTORY_GOT_ANY_ITEM (69), + QUEST_COND_LEARNED_RECIPE (70), + QUEST_COND_LUNARITE_REGION_UNLOCKED (71), + QUEST_COND_LUNARITE_HAS_REGION_HINT_COUNT (72), + QUEST_COND_LUNARITE_COLLECT_FINISH (73), + QUEST_COND_LUNARITE_MARK_ALL_FINISH (74), + QUEST_COND_NEW_HOMEWORLD_SHOP_ITEM (75), + QUEST_COND_SCENE_POINT_UNLOCK (76), + QUEST_COND_SCENE_LEVEL_TAG_EQ (77); + + private final int value; + + QuestCondType(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestExecType.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestExecType.java new file mode 100644 index 000000000..4f3c2557c --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestExecType.java @@ -0,0 +1,82 @@ +package emu.grasscutter.game.quest.enums; + +public enum QuestExecType { + QUEST_EXEC_NONE (0), + QUEST_EXEC_DEL_PACK_ITEM (1), + QUEST_EXEC_UNLOCK_POINT (2), + QUEST_EXEC_UNLOCK_AREA (3), + QUEST_EXEC_UNLOCK_FORCE (4), + QUEST_EXEC_LOCK_FORCE (5), + QUEST_EXEC_CHANGE_AVATAR_ELEMET (6), + QUEST_EXEC_REFRESH_GROUP_MONSTER (7), + QUEST_EXEC_SET_IS_FLYABLE (8), + QUEST_EXEC_SET_IS_WEATHER_LOCKED (9), + QUEST_EXEC_SET_IS_GAME_TIME_LOCKED (10), + QUEST_EXEC_SET_IS_TRANSFERABLE (11), + QUEST_EXEC_GRANT_TRIAL_AVATAR (12), + QUEST_EXEC_OPEN_BORED (13), + QUEST_EXEC_ROLLBACK_QUEST (14), + QUEST_EXEC_NOTIFY_GROUP_LUA (15), + QUEST_EXEC_SET_OPEN_STATE (16), + QUEST_EXEC_LOCK_POINT (17), + QUEST_EXEC_DEL_PACK_ITEM_BATCH (18), + QUEST_EXEC_REFRESH_GROUP_SUITE (19), + QUEST_EXEC_REMOVE_TRIAL_AVATAR (20), + QUEST_EXEC_SET_GAME_TIME (21), + QUEST_EXEC_SET_WEATHER_GADGET (22), + QUEST_EXEC_ADD_QUEST_PROGRESS (23), + QUEST_EXEC_NOTIFY_DAILY_TASK (24), + QUEST_EXEC_CREATE_PATTERN_GROUP (25), + QUEST_EXEC_REMOVE_PATTERN_GROUP (26), + QUEST_EXEC_REFRESH_GROUP_SUITE_RANDOM (27), + QUEST_EXEC_ACTIVE_ITEM_GIVING (28), + QUEST_EXEC_DEL_ALL_SPECIFIC_PACK_ITEM (29), + QUEST_EXEC_ROLLBACK_PARENT_QUEST (30), + QUEST_EXEC_LOCK_AVATAR_TEAM (31), + QUEST_EXEC_UNLOCK_AVATAR_TEAM (32), + QUEST_EXEC_UPDATE_PARENT_QUEST_REWARD_INDEX (33), + QUEST_EXEC_SET_DAILY_TASK_VAR (34), + QUEST_EXEC_INC_DAILY_TASK_VAR (35), + QUEST_EXEC_DEC_DAILY_TASK_VAR (36), + QUEST_EXEC_ACTIVE_ACTIVITY_COND_STATE (37), + QUEST_EXEC_INACTIVE_ACTIVITY_COND_STATE (38), + QUEST_EXEC_ADD_CUR_AVATAR_ENERGY (39), + QUEST_EXEC_START_BARGAIN (41), + QUEST_EXEC_STOP_BARGAIN (42), + QUEST_EXEC_SET_QUEST_GLOBAL_VAR (43), + QUEST_EXEC_INC_QUEST_GLOBAL_VAR (44), + QUEST_EXEC_DEC_QUEST_GLOBAL_VAR (45), + QUEST_EXEC_REGISTER_DYNAMIC_GROUP (46), + QUEST_EXEC_UNREGISTER_DYNAMIC_GROUP (47), + QUEST_EXEC_SET_QUEST_VAR (48), + QUEST_EXEC_INC_QUEST_VAR (49), + QUEST_EXEC_DEC_QUEST_VAR (50), + QUEST_EXEC_RANDOM_QUEST_VAR (51), + QUEST_EXEC_ACTIVATE_SCANNING_PIC (52), + QUEST_EXEC_RELOAD_SCENE_TAG (53), + QUEST_EXEC_REGISTER_DYNAMIC_GROUP_ONLY (54), + QUEST_EXEC_CHANGE_SKILL_DEPOT (55), + QUEST_EXEC_ADD_SCENE_TAG (56), + QUEST_EXEC_DEL_SCENE_TAG (57), + QUEST_EXEC_INIT_TIME_VAR (58), + QUEST_EXEC_CLEAR_TIME_VAR (59), + QUEST_EXEC_MODIFY_CLIMATE_AREA (60), + QUEST_EXEC_GRANT_TRIAL_AVATAR_AND_LOCK_TEAM (61), + QUEST_EXEC_CHANGE_MAP_AREA_STATE (62), + QUEST_EXEC_DEACTIVE_ITEM_GIVING (63), + QUEST_EXEC_CHANGE_SCENE_LEVEL_TAG (64), + QUEST_EXEC_UNLOCK_PLAYER_WORLD_SCENE (65), + QUEST_EXEC_LOCK_PLAYER_WORLD_SCENE (66), + QUEST_EXEC_FAIL_MAINCOOP (67), + QUEST_EXEC_MODIFY_WEATHER_AREA (68); + + private final int value; + + QuestExecType(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestGuideType.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestGuideType.java new file mode 100644 index 000000000..45915c6b7 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestGuideType.java @@ -0,0 +1,17 @@ +package emu.grasscutter.game.quest.enums; + +public enum QuestGuideType { + QUEST_GUIDE_NONE (0), + QUEST_GUIDE_LOCATION (1), + QUEST_GUIDE_NPC (2); + + private final int value; + + QuestGuideType(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestShowType.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestShowType.java new file mode 100644 index 000000000..014c1ee06 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestShowType.java @@ -0,0 +1,16 @@ +package emu.grasscutter.game.quest.enums; + +public enum QuestShowType { + QUEST_SHOW (0), + QUEST_HIDDEN (1); + + private final int value; + + QuestShowType(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestState.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestState.java new file mode 100644 index 000000000..d258a2582 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestState.java @@ -0,0 +1,19 @@ +package emu.grasscutter.game.quest.enums; + +public enum QuestState { + QUEST_STATE_NONE (0), + QUEST_STATE_UNSTARTED (1), + QUEST_STATE_UNFINISHED (2), + QUEST_STATE_FINISHED (3), + QUEST_STATE_FAILED (4); + + private final int value; + + QuestState(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestType.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestType.java new file mode 100644 index 000000000..fbbac2ae0 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestType.java @@ -0,0 +1,22 @@ +package emu.grasscutter.game.quest.enums; + +public enum QuestType { + AQ (0), + FQ (1), + LQ (2), + EQ (3), + DQ (4), + IQ (5), + VQ (6), + WQ (7); + + private final int value; + + QuestType(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/ShowQuestGuideType.java b/src/main/java/emu/grasscutter/game/quest/enums/ShowQuestGuideType.java new file mode 100644 index 000000000..d4e985592 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/ShowQuestGuideType.java @@ -0,0 +1,17 @@ +package emu.grasscutter.game.quest.enums; + +public enum ShowQuestGuideType { + QUEST_GUIDE_ITEM_ENABLE (0), + QUEST_GUIDE_ITEM_DISABLE (1), + QUEST_GUIDE_ITEM_MOVE_HIDE (2); + + private final int value; + + ShowQuestGuideType(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestNotify.java new file mode 100644 index 000000000..7d64da48f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestNotify.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.FinishedParentQuestNotifyOuterClass.FinishedParentQuestNotify; + +public class PacketFinishedParentQuestNotify extends BasePacket { + + public PacketFinishedParentQuestNotify(Player player) { + super(PacketOpcodes.FinishedParentQuestNotify, true); + + FinishedParentQuestNotify.Builder proto = FinishedParentQuestNotify.newBuilder(); + + for (GameMainQuest mainQuest : player.getQuestManager().getQuests().values()) { + proto.addParentQuestList(mainQuest.toProto()); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestUpdateNotify.java new file mode 100644 index 000000000..68eab7222 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestUpdateNotify.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.FinishedParentQuestUpdateNotifyOuterClass.FinishedParentQuestUpdateNotify; + +public class PacketFinishedParentQuestUpdateNotify extends BasePacket { + + public PacketFinishedParentQuestUpdateNotify(GameMainQuest quest) { + super(PacketOpcodes.FinishedParentQuestUpdateNotify); + + FinishedParentQuestUpdateNotify proto = FinishedParentQuestUpdateNotify.newBuilder() + .addParentQuestList(quest.toProto()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListNotify.java new file mode 100644 index 000000000..ccf0d765a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListNotify.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.QuestManager; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.QuestListNotifyOuterClass.QuestListNotify; + +public class PacketQuestListNotify extends BasePacket { + + public PacketQuestListNotify(Player player) { + super(PacketOpcodes.QuestListNotify, true); + + QuestListNotify.Builder proto = QuestListNotify.newBuilder(); + + player.getQuestManager().forEachQuest(quest -> { + proto.addQuestList(quest.toProto()); + }); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListUpdateNotify.java new file mode 100644 index 000000000..adc0767a8 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListUpdateNotify.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.QuestListUpdateNotifyOuterClass.QuestListUpdateNotify; + +public class PacketQuestListUpdateNotify extends BasePacket { + + public PacketQuestListUpdateNotify(GameQuest quest) { + super(PacketOpcodes.QuestListUpdateNotify); + + QuestListUpdateNotify proto = QuestListUpdateNotify.newBuilder() + .addQuestList(quest.toProto()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketQuestProgressUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestProgressUpdateNotify.java new file mode 100644 index 000000000..76ee56316 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestProgressUpdateNotify.java @@ -0,0 +1,30 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.QuestProgressUpdateNotifyOuterClass.QuestProgressUpdateNotify; + +public class PacketQuestProgressUpdateNotify extends BasePacket { + + public PacketQuestProgressUpdateNotify(GameQuest quest) { + super(PacketOpcodes.QuestProgressUpdateNotify); + + QuestProgressUpdateNotify.Builder proto = QuestProgressUpdateNotify.newBuilder().setQuestId(quest.getQuestId()); + + if (quest.getFinishProgressList() != null) { + for (int i : quest.getFinishProgressList()) { + proto.addFinishProgressList(i); + } + } + + if (quest.getFailProgressList() != null) { + for (int i : quest.getFailProgressList()) { + proto.addFailProgressList(i); + } + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java new file mode 100644 index 000000000..b2ea3d577 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java @@ -0,0 +1,37 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.custom.QuestConfig; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ServerCondMeetQuestListUpdateNotifyOuterClass.ServerCondMeetQuestListUpdateNotify; + +public class PacketServerCondMeetQuestListUpdateNotify extends BasePacket { + + public PacketServerCondMeetQuestListUpdateNotify(Player player) { + super(PacketOpcodes.ServerCondMeetQuestListUpdateNotify); + + ServerCondMeetQuestListUpdateNotify.Builder proto = ServerCondMeetQuestListUpdateNotify.newBuilder(); + + player.getQuestManager().forEachQuest(quest -> { + if (quest.getState().getValue() <= 2) { + proto.addAddQuestIdList(quest.getQuestId()); + } + }); + + this.setData(proto); + } + + public PacketServerCondMeetQuestListUpdateNotify(GameQuest quest) { + super(PacketOpcodes.ServerCondMeetQuestListUpdateNotify); + + ServerCondMeetQuestListUpdateNotify proto = ServerCondMeetQuestListUpdateNotify.newBuilder() + .addAddQuestIdList(quest.getQuestId()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 893f490af..107a12d71 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -210,6 +210,14 @@ "success": "Coordinates: %s, %s, %s\nScene id: %s", "description": "Get coordinates." }, + "quest": { + "description": "Add or finish quests", + "usage": "quest [quest id]", + "added": "Quest %s added", + "finished": "Finished quest %s", + "not_found": "Quest not found", + "invalid_id": "Invalid quest id" + }, "reload": { "reload_start": "Reloading config.", "reload_done": "Reload complete.", From dfd8fcb250a5c456dc79affbeab1e3442300788a Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Wed, 11 May 2022 04:01:38 -0700 Subject: [PATCH 087/312] Fix build error from merge --- src/main/java/emu/grasscutter/data/ResourceLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index 92fda7bb1..5c2ac1ee6 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -400,7 +400,7 @@ public class ResourceLoader { } private static void loadQuests() { - File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Quest/"); + File folder = new File(RESOURCE("BinOutput/Quest/")); if (!folder.exists()) { return; From 3c0d85621e2286e2204ae5a23730bd752e35e821 Mon Sep 17 00:00:00 2001 From: ayy lmao Date: Wed, 11 May 2022 17:35:52 +0300 Subject: [PATCH 088/312] Fix InRouting on dispatch server --- .../java/emu/grasscutter/server/dispatch/DispatchServer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index df1ce666d..4e09f8881 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -117,7 +117,7 @@ public final class DispatchServer { .setTitle(DISPATCH_INFO.defaultName) .setType("DEV_PUBLIC") .setDispatchUrl( - "http" + (DISPATCH_ENCRYPTION.useEncryption ? "s" : "") + "://" + "http" + (DISPATCH_ENCRYPTION.useInRouting ? "s" : "") + "://" + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + "/query_cur_region/" + defaultServerName) @@ -150,7 +150,7 @@ public final class DispatchServer { .setTitle(regionInfo.Title) .setType("DEV_PUBLIC") .setDispatchUrl( - "http" + (DISPATCH_ENCRYPTION.useEncryption ? "s" : "") + "://" + "http" + (DISPATCH_ENCRYPTION.useInRouting ? "s" : "") + "://" + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + "/query_cur_region/" + regionInfo.Name) From d215035fc8b969118911db6d12d4c949e4caab88 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 11 May 2022 11:38:30 -0400 Subject: [PATCH 089/312] Refactor config database settings --- .../grasscutter/database/DatabaseHelper.java | 68 +++++++++---------- .../grasscutter/database/DatabaseManager.java | 34 +++++----- .../emu/grasscutter/plugin/PluginManager.java | 3 + .../grasscutter/utils/ConfigContainer.java | 29 ++++---- 4 files changed, 71 insertions(+), 63 deletions(-) diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index 8f1de0bb9..5b622aac8 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -77,25 +77,25 @@ public final class DatabaseHelper { } public static Account getAccountByName(String username) { - return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("username", username)).first(); + return DatabaseManager.getGameDatastore().find(Account.class).filter(Filters.eq("username", username)).first(); } public static Account getAccountByToken(String token) { if(token == null) return null; - return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("token", token)).first(); + return DatabaseManager.getGameDatastore().find(Account.class).filter(Filters.eq("token", token)).first(); } public static Account getAccountBySessionKey(String sessionKey) { if(sessionKey == null) return null; - return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("sessionKey", sessionKey)).first(); + return DatabaseManager.getGameDatastore().find(Account.class).filter(Filters.eq("sessionKey", sessionKey)).first(); } public static Account getAccountById(String uid) { - return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("_id", uid)).first(); + return DatabaseManager.getGameDatastore().find(Account.class).filter(Filters.eq("_id", uid)).first(); } public static Account getAccountByPlayerId(int playerId) { - return DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("playerId", playerId)).first(); + return DatabaseManager.getGameDatastore().find(Account.class).filter(Filters.eq("playerId", playerId)).first(); } public static void deleteAccount(Account target) { @@ -104,37 +104,37 @@ public final class DatabaseHelper { // database in an inconsistent state, but unfortunately Mongo only supports that when we have a replica set ... // Delete Mail.class data - DatabaseManager.getDatabase().getCollection("mail").deleteMany(eq("ownerUid", target.getPlayerUid())); + DatabaseManager.getGameDatabase().getCollection("mail").deleteMany(eq("ownerUid", target.getPlayerUid())); // Delete Avatar.class data - DatabaseManager.getDatabase().getCollection("avatars").deleteMany(eq("ownerId", target.getPlayerUid())); + DatabaseManager.getGameDatabase().getCollection("avatars").deleteMany(eq("ownerId", target.getPlayerUid())); // Delete GachaRecord.class data - DatabaseManager.getDatabase().getCollection("gachas").deleteMany(eq("ownerId", target.getPlayerUid())); + DatabaseManager.getGameDatabase().getCollection("gachas").deleteMany(eq("ownerId", target.getPlayerUid())); // Delete GameItem.class data - DatabaseManager.getDatabase().getCollection("items").deleteMany(eq("ownerId", target.getPlayerUid())); + DatabaseManager.getGameDatabase().getCollection("items").deleteMany(eq("ownerId", target.getPlayerUid())); // Delete friendships. // Here, we need to make sure to not only delete the deleted account's friendships, // but also all friendship entries for that account's friends. - DatabaseManager.getDatabase().getCollection("friendships").deleteMany(eq("ownerId", target.getPlayerUid())); - DatabaseManager.getDatabase().getCollection("friendships").deleteMany(eq("friendId", target.getPlayerUid())); + DatabaseManager.getGameDatabase().getCollection("friendships").deleteMany(eq("ownerId", target.getPlayerUid())); + DatabaseManager.getGameDatabase().getCollection("friendships").deleteMany(eq("friendId", target.getPlayerUid())); // Delete the player. - DatabaseManager.getDatastore().find(Player.class).filter(Filters.eq("id", target.getPlayerUid())).delete(); + DatabaseManager.getGameDatastore().find(Player.class).filter(Filters.eq("id", target.getPlayerUid())).delete(); // Finally, delete the account itself. - DatabaseManager.getDatastore().find(Account.class).filter(Filters.eq("id", target.getId())).delete(); + DatabaseManager.getGameDatastore().find(Account.class).filter(Filters.eq("id", target.getId())).delete(); } public static List getAllPlayers() { - return DatabaseManager.getDatastore().find(Player.class).stream().toList(); + return DatabaseManager.getGameDatastore().find(Player.class).stream().toList(); } public static Player getPlayerById(int id) { - return DatabaseManager.getDatastore().find(Player.class).filter(Filters.eq("_id", id)).first(); + return DatabaseManager.getGameDatastore().find(Player.class).filter(Filters.eq("_id", id)).first(); } public static boolean checkPlayerExists(int id) { - return DatabaseManager.getDatastore().find(Player.class).filter(Filters.eq("_id", id)).first() != null; + return DatabaseManager.getGameDatastore().find(Player.class).filter(Filters.eq("_id", id)).first() != null; } public static synchronized Player createPlayer(Player character, int reservedId) { @@ -151,7 +151,7 @@ public final class DatabaseHelper { character.setUid(id); } // Save to database - DatabaseManager.getDatastore().save(character); + DatabaseManager.getGameDatastore().save(character); return character; } @@ -170,48 +170,48 @@ public final class DatabaseHelper { } public static void savePlayer(Player character) { - DatabaseManager.getDatastore().save(character); + DatabaseManager.getGameDatastore().save(character); } public static void saveAvatar(Avatar avatar) { - DatabaseManager.getDatastore().save(avatar); + DatabaseManager.getGameDatastore().save(avatar); } public static List getAvatars(Player player) { - return DatabaseManager.getDatastore().find(Avatar.class).filter(Filters.eq("ownerId", player.getUid())).stream().toList(); + return DatabaseManager.getGameDatastore().find(Avatar.class).filter(Filters.eq("ownerId", player.getUid())).stream().toList(); } public static void saveItem(GameItem item) { - DatabaseManager.getDatastore().save(item); + DatabaseManager.getGameDatastore().save(item); } public static boolean deleteItem(GameItem item) { - DeleteResult result = DatabaseManager.getDatastore().delete(item); + DeleteResult result = DatabaseManager.getGameDatastore().delete(item); return result.wasAcknowledged(); } public static List getInventoryItems(Player player) { - return DatabaseManager.getDatastore().find(GameItem.class).filter(Filters.eq("ownerId", player.getUid())).stream().toList(); + return DatabaseManager.getGameDatastore().find(GameItem.class).filter(Filters.eq("ownerId", player.getUid())).stream().toList(); } public static List getFriends(Player player) { - return DatabaseManager.getDatastore().find(Friendship.class).filter(Filters.eq("ownerId", player.getUid())).stream().toList(); + return DatabaseManager.getGameDatastore().find(Friendship.class).filter(Filters.eq("ownerId", player.getUid())).stream().toList(); } public static List getReverseFriends(Player player) { - return DatabaseManager.getDatastore().find(Friendship.class).filter(Filters.eq("friendId", player.getUid())).stream().toList(); + return DatabaseManager.getGameDatastore().find(Friendship.class).filter(Filters.eq("friendId", player.getUid())).stream().toList(); } public static void saveFriendship(Friendship friendship) { - DatabaseManager.getDatastore().save(friendship); + DatabaseManager.getGameDatastore().save(friendship); } public static void deleteFriendship(Friendship friendship) { - DatabaseManager.getDatastore().delete(friendship); + DatabaseManager.getGameDatastore().delete(friendship); } public static Friendship getReverseFriendship(Friendship friendship) { - return DatabaseManager.getDatastore().find(Friendship.class).filter(Filters.and( + return DatabaseManager.getGameDatastore().find(Friendship.class).filter(Filters.and( Filters.eq("ownerId", friendship.getFriendId()), Filters.eq("friendId", friendship.getOwnerId()) )).first(); @@ -222,7 +222,7 @@ public final class DatabaseHelper { } public static List getGachaRecords(int ownerId, int page, int gachaType, int pageSize){ - return DatabaseManager.getDatastore().find(GachaRecord.class).filter( + return DatabaseManager.getGameDatastore().find(GachaRecord.class).filter( Filters.eq("ownerId", ownerId), Filters.eq("gachaType", gachaType) ).iterator(new FindOptions() @@ -237,7 +237,7 @@ public final class DatabaseHelper { } public static long getGachaRecordsMaxPage(int ownerId, int page, int gachaType, int pageSize){ - long count = DatabaseManager.getDatastore().find(GachaRecord.class).filter( + long count = DatabaseManager.getGameDatastore().find(GachaRecord.class).filter( Filters.eq("ownerId", ownerId), Filters.eq("gachaType", gachaType) ).count(); @@ -245,19 +245,19 @@ public final class DatabaseHelper { } public static void saveGachaRecord(GachaRecord gachaRecord){ - DatabaseManager.getDatastore().save(gachaRecord); + DatabaseManager.getGameDatastore().save(gachaRecord); } public static List getAllMail(Player player) { - return DatabaseManager.getDatastore().find(Mail.class).filter(Filters.eq("ownerUid", player.getUid())).stream().toList(); + return DatabaseManager.getGameDatastore().find(Mail.class).filter(Filters.eq("ownerUid", player.getUid())).stream().toList(); } public static void saveMail(Mail mail) { - DatabaseManager.getDatastore().save(mail); + DatabaseManager.getGameDatastore().save(mail); } public static boolean deleteMail(Mail mail) { - DeleteResult result = DatabaseManager.getDatastore().delete(mail); + DeleteResult result = DatabaseManager.getGameDatastore().delete(mail); return result.wasAcknowledged(); } } diff --git a/src/main/java/emu/grasscutter/database/DatabaseManager.java b/src/main/java/emu/grasscutter/database/DatabaseManager.java index 37bda042b..37ec0094d 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseManager.java +++ b/src/main/java/emu/grasscutter/database/DatabaseManager.java @@ -23,19 +23,19 @@ import emu.grasscutter.game.player.Player; import static emu.grasscutter.Configuration.*; public final class DatabaseManager { - private static Datastore datastore; + private static Datastore gameDatastore; private static Datastore dispatchDatastore; private static final Class[] mappedClasses = new Class[] { DatabaseCounter.class, Account.class, Player.class, Avatar.class, GameItem.class, Friendship.class, GachaRecord.class, Mail.class }; - public static Datastore getDatastore() { - return datastore; + public static Datastore getGameDatastore() { + return gameDatastore; } - public static MongoDatabase getDatabase() { - return getDatastore().getDatabase(); + public static MongoDatabase getGameDatabase() { + return getGameDatastore().getDatabase(); } // Yes. I very dislike this method. However, this will be good for now. @@ -44,42 +44,42 @@ public final class DatabaseManager { if(SERVER.runMode == ServerRunMode.GAME_ONLY) { return dispatchDatastore; } else { - return datastore; + return gameDatastore; } } public static void initialize() { // Initialize - MongoClient mongoClient = MongoClients.create(DATABASE.connectionUri); + MongoClient gameMongoClient = MongoClients.create(DATABASE.game.connectionUri); // Set mapper options. MapperOptions mapperOptions = MapperOptions.builder() .storeEmpties(true).storeNulls(false).build(); // Create data store. - datastore = Morphia.createDatastore(mongoClient, DATABASE.collection, mapperOptions); + gameDatastore = Morphia.createDatastore(gameMongoClient, DATABASE.game.collection, mapperOptions); // Map classes. - datastore.getMapper().map(mappedClasses); + gameDatastore.getMapper().map(mappedClasses); // Ensure indexes try { - datastore.ensureIndexes(); + gameDatastore.ensureIndexes(); } catch (MongoCommandException exception) { Grasscutter.getLogger().info("Mongo index error: ", exception); // Duplicate index error if (exception.getCode() == 85) { // Drop all indexes and re add them - MongoIterable collections = datastore.getDatabase().listCollectionNames(); + MongoIterable collections = gameDatastore.getDatabase().listCollectionNames(); for (String name : collections) { - datastore.getDatabase().getCollection(name).dropIndexes(); + gameDatastore.getDatabase().getCollection(name).dropIndexes(); } // Add back indexes - datastore.ensureIndexes(); + gameDatastore.ensureIndexes(); } } if(SERVER.runMode == ServerRunMode.GAME_ONLY) { - MongoClient dispatchMongoClient = MongoClients.create(GAME_OPTIONS.databaseInfo.connectionUri); - dispatchDatastore = Morphia.createDatastore(dispatchMongoClient, GAME_OPTIONS.databaseInfo.collection); + MongoClient dispatchMongoClient = MongoClients.create(DATABASE.server.connectionUri); + dispatchDatastore = Morphia.createDatastore(dispatchMongoClient, DATABASE.server.collection); // Ensure indexes for dispatch server try { @@ -101,14 +101,14 @@ public final class DatabaseManager { } public static synchronized int getNextId(Class c) { - DatabaseCounter counter = getDatastore().find(DatabaseCounter.class).filter(Filters.eq("_id", c.getSimpleName())).first(); + DatabaseCounter counter = getGameDatastore().find(DatabaseCounter.class).filter(Filters.eq("_id", c.getSimpleName())).first(); if (counter == null) { counter = new DatabaseCounter(c.getSimpleName()); } try { return counter.getNextId(); } finally { - getDatastore().save(counter); + getGameDatastore().save(counter); } } diff --git a/src/main/java/emu/grasscutter/plugin/PluginManager.java b/src/main/java/emu/grasscutter/plugin/PluginManager.java index 5974cac44..5d58744a4 100644 --- a/src/main/java/emu/grasscutter/plugin/PluginManager.java +++ b/src/main/java/emu/grasscutter/plugin/PluginManager.java @@ -7,6 +7,7 @@ import emu.grasscutter.server.event.HandlerPriority; import emu.grasscutter.utils.Utils; import java.io.File; +import java.io.FileNotFoundException; import java.io.InputStreamReader; import java.lang.reflect.Method; import java.net.MalformedURLException; @@ -90,6 +91,8 @@ public final class PluginManager { fileReader.close(); // Close the file reader. } catch (ClassNotFoundException ignored) { Grasscutter.getLogger().warn("Plugin " + plugin.getName() + " has an invalid main class."); + } catch (FileNotFoundException ignored) { + Grasscutter.getLogger().warn("Plugin " + plugin.getName() + " lacks a valid config file."); } } catch (Exception exception) { Grasscutter.getLogger().error("Failed to load plugin: " + plugin.getName(), exception); diff --git a/src/main/java/emu/grasscutter/utils/ConfigContainer.java b/src/main/java/emu/grasscutter/utils/ConfigContainer.java index 0a453191c..76556700c 100644 --- a/src/main/java/emu/grasscutter/utils/ConfigContainer.java +++ b/src/main/java/emu/grasscutter/utils/ConfigContainer.java @@ -2,6 +2,8 @@ package emu.grasscutter.utils; import com.google.gson.JsonObject; import emu.grasscutter.Grasscutter; +import emu.grasscutter.Grasscutter.ServerDebugMode; +import emu.grasscutter.Grasscutter.ServerRunMode; import java.io.FileReader; import java.lang.reflect.Field; @@ -15,7 +17,7 @@ import static emu.grasscutter.Grasscutter.config; */ public class ConfigContainer { private static int version() { - return 1; + return 2; } /** @@ -69,8 +71,13 @@ public class ConfigContainer { /* Option containers. */ public static class Database { - public String connectionUri = "mongodb://localhost:27017"; - public String collection = "grasscutter"; + public DataStore server = new DataStore(); + public DataStore game = new DataStore(); + + public static class DataStore { + public String connectionUri = "mongodb://localhost:27017"; + public String collection = "grasscutter"; + } } public static class Structure { @@ -86,8 +93,8 @@ public class ConfigContainer { } public static class Server { - public Grasscutter.ServerDebugMode debugLevel = Grasscutter.ServerDebugMode.NONE; - public Grasscutter.ServerRunMode runMode = Grasscutter.ServerRunMode.HYBRID; + public ServerDebugMode debugLevel = ServerDebugMode.NONE; + public ServerRunMode runMode = ServerRunMode.HYBRID; public Dispatch dispatch = new Dispatch(); public Game game = new Game(); @@ -112,7 +119,7 @@ public class ConfigContainer { public int bindPort = 443; /* This is the port used in URLs. */ - public int accessPort = 443; + public int accessPort = 0; public Encryption encryption = new Encryption(); public Policies policies = new Policies(); @@ -128,7 +135,7 @@ public class ConfigContainer { public int bindPort = 22102; /* This is the port used in the default region. */ - public int accessPort = 22102; + public int accessPort = 0; public GameOptions gameOptions = new GameOptions(); public JoinOptions joinOptions = new JoinOptions(); @@ -155,16 +162,14 @@ public class ConfigContainer { } public static class GameOptions { - public GameOptions.InventoryLimits inventoryLimits = new GameOptions.InventoryLimits(); - public GameOptions.AvatarLimits avatarLimits = new GameOptions.AvatarLimits(); + public InventoryLimits inventoryLimits = new InventoryLimits(); + public AvatarLimits avatarLimits = new AvatarLimits(); public int worldEntityLimit = 1000; // Unenforced. TODO: Implement. public boolean watchGachaConfig = false; public boolean enableShopItems = true; public boolean staminaUsage = true; - public GameOptions.Rates rates = new GameOptions.Rates(); - - public Database databaseInfo = new Database(); + public Rates rates = new Rates(); public static class InventoryLimits { public int weapons = 2000; From 03a287623375d619c8c1fb53e4c64132c7fe7cd8 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 11 May 2022 11:46:36 -0400 Subject: [PATCH 090/312] Add plugin loggers --- src/main/java/emu/grasscutter/plugin/Plugin.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/emu/grasscutter/plugin/Plugin.java b/src/main/java/emu/grasscutter/plugin/Plugin.java index f1ce18a6b..b45e642a5 100644 --- a/src/main/java/emu/grasscutter/plugin/Plugin.java +++ b/src/main/java/emu/grasscutter/plugin/Plugin.java @@ -3,6 +3,8 @@ package emu.grasscutter.plugin; import emu.grasscutter.Grasscutter; import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.server.game.GameServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.InputStream; @@ -19,6 +21,7 @@ public abstract class Plugin { private PluginIdentifier identifier; private URLClassLoader classLoader; private File dataFolder; + private Logger logger; /** * This method is reflected into. @@ -35,6 +38,7 @@ public abstract class Plugin { this.identifier = identifier; this.classLoader = classLoader; this.dataFolder = new File(PLUGINS_FOLDER, identifier.name); + this.logger = LoggerFactory.getLogger(identifier.name); if(!this.dataFolder.exists() && !this.dataFolder.mkdirs()) { Grasscutter.getLogger().warn("Failed to create plugin data folder for " + this.identifier.name); @@ -103,6 +107,14 @@ public abstract class Plugin { public final ServerHook getHandle() { return this.server; } + + /** + * Returns the plugin's logger. + * @return A SLF4J logger. + */ + public final Logger getLogger() { + return this.logger; + } /* Called when the plugin is first loaded. */ public void onLoad() { } From 9ed1bb9b940d98cbba73528bc3e0ea02dcafedd0 Mon Sep 17 00:00:00 2001 From: Secretboy-SMR Date: Wed, 11 May 2022 21:14:07 +0800 Subject: [PATCH 091/312] It will use the english as default rather than tell you the value is not exist if there's no translation for currently language --- src/main/java/emu/grasscutter/utils/Language.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/utils/Language.java b/src/main/java/emu/grasscutter/utils/Language.java index 3789f594a..c343e949e 100644 --- a/src/main/java/emu/grasscutter/utils/Language.java +++ b/src/main/java/emu/grasscutter/utils/Language.java @@ -160,7 +160,9 @@ public final class Language { JsonObject object = this.languageData; int index = 0; - String result = "This value does not exist. Please report this to the Discord: " + key; + String valueNotFoundPattern = "This value does not exist. Please report this to the Discord: "; + String result = valueNotFoundPattern + key; + boolean isValueFound = false; while (true) { if(index == keys.length) break; @@ -171,10 +173,18 @@ public final class Language { if(element.isJsonObject()) object = element.getAsJsonObject(); else { + isValueFound = true; result = element.getAsString(); break; } } else break; } + + if (!isValueFound && !languageCode.equals("en-US")) { + var englishValue = Grasscutter.getLanguage("en-US").get(key); + if (!englishValue.contains(valueNotFoundPattern)) { + result += "\nhere is english version:\n" + englishValue; + } + } this.cachedTranslations.put(key, result); return result; } From c932f9c7e5389868c958456f2cdb33ddf3d6382b Mon Sep 17 00:00:00 2001 From: Benjamin Elsdon Date: Wed, 11 May 2022 20:35:27 +0800 Subject: [PATCH 092/312] Add verifyUser to AuthenticationHandler --- .../dispatch/authentication/AuthenticationHandler.java | 7 +++++++ .../authentication/DefaultAuthenticationHandler.java | 6 ++++++ src/main/resources/languages/en-US.json | 3 +++ 3 files changed, 16 insertions(+) diff --git a/src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java b/src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java index 92a2961ea..e644a9f1d 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java +++ b/src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java @@ -12,5 +12,12 @@ public interface AuthenticationHandler { void handleRegister(Request req, Response res); void handleChangePassword(Request req, Response res); + /** + * Other plugins may need to verify a user's identity using details from handleLogin() + * @param details The user's unique one-time token that needs to be verified + * @return If the verification was successful + */ + boolean verifyUser(String details); + LoginResultJson handleGameLogin(Request req, LoginAccountRequestJson requestData); } diff --git a/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java b/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java index 67b3d4023..122e04ff6 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java +++ b/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java @@ -28,6 +28,12 @@ public class DefaultAuthenticationHandler implements AuthenticationHandler { res.send("Authentication is not available with the default authentication method"); } + @Override + public boolean verifyUser(String details) { + Grasscutter.getLogger().info(translate("dispatch.authentication.default_unable_to_verify")); + return false; + } + @Override public LoginResultJson handleGameLogin(Request req, LoginAccountRequestJson requestData) { LoginResultJson responseData = new LoginResultJson(); diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index c9c3c0c70..2b392b682 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -16,6 +16,9 @@ "no_keystore_error": "[Dispatch] No SSL cert found! Falling back to HTTP server.", "default_password": "[Dispatch] The default keystore password was loaded successfully. Please consider setting the password to 123456 in config.json." }, + "authentication": { + "default_unable_to_verify": "[Authentication] Something called the verifyUser method which is unavailable in the default authentication handler" + }, "no_commands_error": "Commands are not supported in dispatch only mode.", "unhandled_request_error": "[Dispatch] Potential unhandled %s request: %s", "account": { From 99de46e2618a23b9012184a5c431e0caf964946a Mon Sep 17 00:00:00 2001 From: tester233 <105267106+tester233@users.noreply.github.com> Date: Wed, 11 May 2022 18:25:01 +0800 Subject: [PATCH 093/312] Improve text & remove extra punctuation --- src/main/resources/languages/zh-CN.json | 100 ++++++++++++------------ 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 2f9663f4f..01572a20b 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -55,7 +55,7 @@ "permission_error": "哼哼哼!你没有执行此命令的权限!请联系服务器管理员解决!", "console_execute_error": "此命令只能在服务器控制台执行呐~", "player_execute_error": "此命令只能在游戏内执行哦~", - "command_exist_error": "这条命令……好像找不到呢?。", + "command_exist_error": "这条命令...好像找不到呢?", "no_description_specified": "没有指定说明", "invalid": { "amount": "无效的数量。", @@ -97,19 +97,19 @@ "delete": "账号已删除。", "no_account": "账号不存在。", "command_usage": "用法:account <用户名> [uid]", - "description": "创建或删除账号。" + "description": "创建或删除账号" }, "broadcast": { "command_usage": "用法:broadcast <消息>", "message_sent": "公告已发送。", - "description": "向所有玩家发送公告。" + "description": "向所有玩家发送公告" }, "changescene": { "usage": "用法:changescene <场景ID>", "already_in_scene": "你已经在这个场景中了。", "success": "已切换至场景 %s。", "exists_error": "此场景不存在。", - "description": "切换指定场景。" + "description": "切换指定场景" }, "clear": { "command_usage": "用法:clear \nall: 所有, wp: 武器, art: 圣遗物, mat: 材料", @@ -120,32 +120,32 @@ "displays": "已清空 %s 的屏幕。", "virtuals": "已清除 %s 的所有货币和经验值。", "everything": "已清除 %s 的所有物品。", - "description": "从你的背包中删除所有未装备且已解锁的物品,包括稀有物品。" + "description": "从你的背包中删除所有未装备且已解锁的物品,包括稀有物品" }, "coop": { "usage": "用法:coop <玩家ID> <目标玩家ID>", - "success": "已强制传送 %s 到 %s 的世界", - "description": "强制传送指定用户到他人的世界。" + "success": "已强制传送 %s 到 %s 的世界。", + "description": "强制传送指定用户到他人的世界" }, "enter_dungeon": { "usage": "用法:enterdungeon <秘境ID>", - "changed": "已进入秘境 %s", + "changed": "已进入秘境 %s。", "not_found_error": "此秘境不存在。", "in_dungeon_error": "你已经在秘境中了。", - "description": "进入指定秘境。" + "description": "进入指定秘境" }, "giveAll": { "usage": "用法:giveall [玩家] [数量]", "started": "正在给予全部物品...", "success": "已给予 %s 全部物品。", "invalid_amount_or_playerId": "无效的数量/玩家ID。", - "description": "给予所有物品。" + "description": "给予所有物品" }, "giveArtifact": { "usage": "用法:giveart|gart [玩家] <圣遗物ID> <主词条ID> [<副词条ID>[,<强化次数>]]... [等级]", "id_error": "无效的圣遗物ID。", "success": "已将 %s 给予 %s。", - "description": "给予指定圣遗物。" + "description": "给予指定圣遗物" }, "giveChar": { "usage": "用法:givechar <玩家> <角色ID|角色名> [数量]", @@ -153,50 +153,50 @@ "invalid_avatar_id": "无效的角色ID。", "invalid_avatar_level": "无效的角色等级。", "invalid_avatar_or_player_id": "无效的角色ID/玩家ID。", - "description": "给予指定角色。" + "description": "给予指定角色" }, "give": { "usage": "用法:give <玩家> <物品ID|物品名> [数量] [等级] [精炼等级]", "refinement_only_applicable_weapons": "只有武器可以设置精炼等级。", "refinement_must_between_1_and_5": "精炼等级必须在 1 到 5 之间。", "given": "已将 %s 个 %s 给予 %s。", - "given_with_level_and_refinement": "已将 %s (等级 %s, 精炼 %s) %s 个给予 %s", - "given_level": "已将 %s (等级 %s) %s 个给予 %s", - "description": "给予指定物品。" + "given_with_level_and_refinement": "已将 %s (等级 %s, 精炼 %s) %s 个给予 %s。", + "given_level": "已将 %s (等级 %s) %s 个给予 %s。", + "description": "给予指定物品" }, "godmode": { - "success": "%s 的无敌模式已被设置为 %s。", - "description": "防止你受到伤害。" + "success": "%s 的上帝模式已被设置为 %s。", + "description": "防止你受到伤害" }, "heal": { "success": "已经治疗所有角色。", - "description": "治疗当前队伍的角色。" + "description": "治疗当前队伍的角色" }, "kick": { - "player_kick_player": "玩家 [%s:%s] 已将 [%s:%s] 踢出", - "server_kick_player": "正在踢出玩家 [%s:%s]", - "description": "从服务器内踢出指定玩家。" + "player_kick_player": "玩家 [%s:%s] 已将 [%s:%s] 踢出。", + "server_kick_player": "正在踢出玩家 [%s:%s]...", + "description": "从服务器内踢出指定玩家" }, "kill": { "usage": "用法:killall [玩家UID] [场景ID]", - "scene_not_found_in_player_world": "未在玩家世界中找到此场景", + "scene_not_found_in_player_world": "未在玩家世界中找到此场景。", "kill_monsters_in_scene": "已杀死场景 %s 中的 %s 个怪物。", - "description": "杀死所有怪物。" + "description": "杀死所有怪物" }, "killCharacter": { "usage": "用法:/killcharacter [玩家ID]", "success": "已杀死 %s 当前角色。", - "description": "杀死当前角色。" + "description": "杀死当前角色" }, "language": { "current_language": "当前语言是: %s", "language_changed": "语言切换至: %s", "language_not_found": "目前服务端没有这种语言: %s", - "description": "显示或切换当前语言。" + "description": "显示或切换当前语言" }, "list": { "success": "目前在线人数:%s", - "description": "查看所有玩家。" + "description": "查看所有玩家" }, "permission": { "usage": "用法:permission <用户名> <权限>", @@ -205,25 +205,25 @@ "remove": "权限已移除。", "not_have_error": "此玩家未拥有权限!", "account_error": "账号不存在。", - "description": "添加或移除指定玩家的权限。" + "description": "添加或移除指定玩家的权限" }, "position": { "success": "坐标:%s, %s, %s\n场景ID:%s", - "description": "获取所在位置。" + "description": "获取所在位置" }, "reload": { "reload_start": "正在重载配置文件和数据。", "reload_done": "重载完成。", - "description": "重载配置文件和数据。" + "description": "重载配置文件和数据" }, "resetConst": { "reset_all": "重置所有角色的命座。", "success": "已重置 %s 的命座,重新登录后生效。", - "description": "重置当前角色的命之座,执行命令后需重新登录以生效。" + "description": "重置当前角色的命之座,执行命令后需重新登录以生效" }, "resetShopLimit": { "usage": "用法:/resetshop <玩家ID>", - "description": "重置所选玩家的商店刷新时间。" + "description": "重置所选玩家的商店刷新时间" }, "sendMail": { "usage": "用法:give [玩家] <物品ID|物品名称> [数量]", @@ -246,19 +246,19 @@ "sender": "<发件人>", "arguments": "<物品ID|物品名称|finish> [数量] [等级]", "error": "错误:无效的编写阶段 %s。需要 StackTrace 请查看服务器控制台。", - "description": "向指定用户发送邮件。此命令的用法可根据附加的参数而变化。" + "description": "向指定用户发送邮件。此命令的用法可根据附加的参数而变化" }, "sendMessage": { "usage": "用法:sendmessage <玩家> <消息>", "success": "消息已发送。", - "description": "向指定玩家发送消息。" + "description": "向指定玩家发送消息" }, "setFetterLevel": { "usage": "用法:setfetterlevel <好感度等级>", "range_error": "好感度等级必须在 0 到 10 之间。", - "success": "好感度已设置为 %s 级", + "success": "好感度已设置为 %s 级。", "level_error": "无效的好感度等级。", - "description": "设置当前角色的好感度等级。" + "description": "设置当前角色的好感度等级" }, "setStats": { "usage_console": "用法:setstats|stats @ <属性> <数值>", @@ -270,23 +270,23 @@ "set_self": "%s 已设为 %s。", "set_for_uid": "将 %s (来自 %s) 设置为 %s。", "set_max_hp": "最大生命值已设为 %s。", - "description": "设置当前角色的属性。" + "description": "设置当前角色的属性" }, "setWorldLevel": { "usage": "用法:setworldlevel <等级>", "value_error": "世界等级必须设置在0-8之间。", "success": "已将世界等级设为 %s。", "invalid_world_level": "无效的世界等级。", - "description": "设置世界等级,执行命令后需重新登录以生效。" + "description": "设置世界等级,执行命令后需重新登录以生效" }, "spawn": { "usage": "用法:spawn <实体ID> [数量] [等级(仅怪物)]", "success": "已生成 %s 个 %s。", - "description": "在你附近生成一个生物。" + "description": "在你附近生成一个生物" }, "stop": { "success": "正在关闭服务器...", - "description": "停止服务器。" + "description": "停止服务器" }, "talent": { "usage_1": "设置天赋等级:/talent set <天赋ID> <数值>", @@ -303,20 +303,20 @@ "normal_attack_id": "普通攻击的 ID 为 %s。", "e_skill_id": "元素战技ID %s。", "q_skill_id": "元素爆发ID %s。", - "description": "设置当前角色的天赋等级。" + "description": "设置当前角色的天赋等级" }, "teleportAll": { - "success": "已将所有玩家传送到你的位置", + "success": "已将所有玩家传送到你的位置。", "error": "你只能在多人游戏状态下执行此命令。", - "description": "将你世界中的所有玩家传送到你所在的位置。" + "description": "将你世界中的所有玩家传送到你所在的位置" }, "teleport": { "usage_server": "用法:/tp @<玩家ID> [场景ID]", "usage": "用法:/tp [@<玩家ID>] [场景ID]", "specify_player_id": "你必须指定一个玩家ID。", "invalid_position": "无效的位置。", - "success": "传送 %s 到坐标 %s,%s,%s,场景为 %s", - "description": "改变指定玩家的位置。" + "success": "传送 %s 到坐标 %s,%s,%s,场景为 %s。", + "description": "改变指定玩家的位置" }, "tower": { "unlock_done": "深境回廊的所有层已全部解锁。" @@ -325,28 +325,28 @@ "usage": "用法:weather <天气ID> [气候ID]", "success": "已更改天气为 %s,气候为 %s。", "invalid_id": "无效的天气ID。", - "description": "更改天气。" + "description": "更改天气" }, "drop": { "command_usage": "用法:drop <物品ID|物品名称> [数量]", "success": "已丢下 %s 个 %s。", - "description": "在你附近丢下一个物品。" + "description": "在你附近丢下一个物品" }, "help": { "usage": "用法:", "aliases": "别名:", "available_commands": "可用命令:", - "description": "发送帮助信息或显示指定命令的信息。" + "description": "发送帮助信息或显示指定命令的信息" }, "restart": { - "description": "重新启动服务器。" + "description": "重新启动服务器" }, "unlocktower": { "success": "解锁完成。", - "description": "解锁深境螺旋的所有层。" + "description": "解锁深境螺旋的所有层" }, "resetshop": { - "description": "重置商店刷新时间。" + "description": "重置商店刷新时间" } } } From 9fc18151c94e4d6c0aa96d4169db7eb12586252f Mon Sep 17 00:00:00 2001 From: tester233 <105267106+tester233@users.noreply.github.com> Date: Wed, 11 May 2022 19:37:52 +0800 Subject: [PATCH 094/312] Improve text --- src/main/resources/languages/zh-CN.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 01572a20b..111e3c0c5 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -20,7 +20,7 @@ "unhandled_request_error": "[Dispatch] 潜在的未处理请求:%s %s", "account": { "login_attempt": "[Dispatch] 客户端 %s 正在尝试登录", - "login_success": "[Dispatch] 客户端 %s 已登录,UID为 %s", + "login_success": "[Dispatch] 客户端 %s 已登录,UID 为 %s", "login_token_attempt": "[Dispatch] 客户端 %s 正在尝试使用 token 登录", "login_token_error": "[Dispatch] 客户端 %s 使用 token 登录失败", "login_token_success": "[Dispatch] 客户端 %s 已通过 token 登录,UID 为 %s", @@ -96,7 +96,7 @@ "create": "已创建账号,UID 为 %s。", "delete": "账号已删除。", "no_account": "账号不存在。", - "command_usage": "用法:account <用户名> [uid]", + "command_usage": "用法:account <用户名> [UID]", "description": "创建或删除账号" }, "broadcast": { @@ -290,7 +290,7 @@ }, "talent": { "usage_1": "设置天赋等级:/talent set <天赋ID> <数值>", - "usage_2": "另一种设置天赋等级的方法:/talent <数值>", + "usage_2": "另一种设置天赋等级的方法:/talent <数值>", "usage_3": "获取天赋ID:/talent getid", "lower_16": "无效的天赋等级,天赋等级应小于等于15。", "set_id": "将天赋等级设为 %s。", From a77ae0bc3c1001997eb29d0bf23c5e83113a5b70 Mon Sep 17 00:00:00 2001 From: mingjun97 Date: Tue, 10 May 2022 18:34:30 -0700 Subject: [PATCH 095/312] Introduce `-version` argument to display version --- build.gradle | 16 ++++++++++++++++ src/main/java/emu/grasscutter/Grasscutter.java | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/build.gradle b/build.gradle index 4434ed28e..186a6d440 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,14 @@ targetCompatibility = JavaVersion.VERSION_17 group = 'xyz.grasscutters' version = '1.1.1-dev' +def gitCommitHash = { + try { + return 'git rev-parse --verify --short HEAD'.execute().text.trim() + } catch (e) { + return "GIT_NOT_FOUND" + } +} + sourceCompatibility = 17 targetCompatibility = 17 @@ -97,6 +105,7 @@ application { mainClassName = 'emu.grasscutter.Grasscutter' } + jar { manifest { attributes 'Main-Class': 'emu.grasscutter.Grasscutter' @@ -113,6 +122,13 @@ jar { from('src/main/java') { include '*.xml' } + new File(projectDir, "src/generated/main/java/emu/grasscutter/BuildConfig.java").text = """ + package emu.grasscutter; + public class BuildConfig { + public static final String VERSION = \"${version}\"; + public static final String GIT_HASH = \"${gitCommitHash()}\"; + } + """ destinationDir = file(".") } diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 73e761e6e..a192815d1 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -29,6 +29,7 @@ import emu.grasscutter.server.dispatch.DispatchServer; import emu.grasscutter.server.game.GameServer; import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.Crypto; +import emu.grasscutter.BuildConfig; import javax.annotation.Nullable; @@ -82,6 +83,9 @@ public final class Grasscutter { case "-gachamap" -> { Tools.createGachaMapping(DATA("gacha_mappings.js")); exitEarly = true; } + case "-version" -> { + System.out.println("Grasscutter version: " + BuildConfig.VERSION + "\nGit Hash: " + BuildConfig.GIT_HASH); exitEarly = true; + } } } From 170db70b6fc503488b911a9acdbceeae73426815 Mon Sep 17 00:00:00 2001 From: mingjun97 Date: Tue, 10 May 2022 21:11:19 -0700 Subject: [PATCH 096/312] Fix github action build issue * Move `BuildConfig.java` from `/src/generated`to `/src/main` to accomplish the building pipeline * Add BuildConfig.java to the .gitignore --- .gitignore | 1 + build.gradle | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9fa6c9427..6fd78ed3b 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,7 @@ language/ languages/ gacha-mapping.js data/gacha_mappings.js +BuildConfig.java # macOS .DS_Store diff --git a/build.gradle b/build.gradle index 186a6d440..47f433b57 100644 --- a/build.gradle +++ b/build.gradle @@ -122,7 +122,7 @@ jar { from('src/main/java') { include '*.xml' } - new File(projectDir, "src/generated/main/java/emu/grasscutter/BuildConfig.java").text = """ + new File(projectDir, "src/main/java/emu/grasscutter/BuildConfig.java").text = """ package emu.grasscutter; public class BuildConfig { public static final String VERSION = \"${version}\"; From 895e2bc44ae321b19ccf3c397cce49eb44fc523c Mon Sep 17 00:00:00 2001 From: mingjun97 Date: Tue, 10 May 2022 21:36:30 -0700 Subject: [PATCH 097/312] Display version info at console starting --- src/main/java/emu/grasscutter/Grasscutter.java | 1 + src/main/resources/languages/en-US.json | 3 ++- src/main/resources/languages/pl-PL.json | 3 ++- src/main/resources/languages/zh-CN.json | 3 ++- src/main/resources/languages/zh-TW.json | 3 ++- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index a192815d1..b328a453b 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -190,6 +190,7 @@ public final class Grasscutter { } getLogger().info(translate("messages.status.done")); + getLogger().info(translate("messages.status.version", BuildConfig.VERSION, BuildConfig.GIT_HASH)); String input = null; boolean isLastInterrupted = false; while (true) { diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 2b392b682..099e36eaa 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -48,7 +48,8 @@ "run_mode_error": "Invalid server run mode: %s.", "run_mode_help": "Server run mode must be 'HYBRID', 'DISPATCH_ONLY', or 'GAME_ONLY'. Unable to start Grasscutter...", "create_resources": "Creating resources folder...", - "resources_error": "Place a copy of 'BinOutput' and 'ExcelBinOutput' in the resources folder." + "resources_error": "Place a copy of 'BinOutput' and 'ExcelBinOutput' in the resources folder.", + "version": "Grasscutter version: %s, Git Hash: %s" } }, "commands": { diff --git a/src/main/resources/languages/pl-PL.json b/src/main/resources/languages/pl-PL.json index 8f76d8951..0f5d88aa9 100644 --- a/src/main/resources/languages/pl-PL.json +++ b/src/main/resources/languages/pl-PL.json @@ -45,7 +45,8 @@ "run_mode_error": "Błędny tryb pracy serwera: %s.", "run_mode_help": "Tryb pracy serwera musi być ustawiony na 'HYBRID', 'DISPATCH_ONLY', lub 'GAME_ONLY'. Nie można wystartować Grasscutter...", "create_resources": "Tworzenie folderu resources...", - "resources_error": "Umieść kopię 'BinOutput' i 'ExcelBinOutput' w folderze resources." + "resources_error": "Umieść kopię 'BinOutput' i 'ExcelBinOutput' w folderze resources.", + "version": "Grasscutter versión: %s, Git Hash: %s" } }, "commands": { diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 111e3c0c5..5f2b02736 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -45,7 +45,8 @@ "run_mode_error": "无效的服务器运行模式:%s。", "run_mode_help": "服务器运行模式必须为 HYBRID、DISPATCH_ONLY 或 GAME_ONLY。Grasscutter 启动失败...", "create_resources": "正在创建 resources 目录...", - "resources_error": "请将 BinOutput 和 ExcelBinOutput 复制到 resources 目录。" + "resources_error": "请将 BinOutput 和 ExcelBinOutput 复制到 resources 目录。", + "version": "Grasscutter版本: %s, Git Hash: %s" } }, "commands": { diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index 9c3c99686..3cb850415 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -45,7 +45,8 @@ "run_mode_error": "無效的伺服器運行模式: %s。", "run_mode_help": "伺服器運行模式必須為 HYBRID 或者 DISPATCH_ONLY 或者 GAME_ONLY。Grasscutter 啟動失敗...", "create_resources": "正在建立 resources 資料夾...", - "resources_error": "請將 BinOutput 和 ExcelBinOutput 複製到 resources 資料夾。" + "resources_error": "請將 BinOutput 和 ExcelBinOutput 複製到 resources 資料夾。", + "version": "Grasscutter版本: %s, Git Hash: %s" } }, "commands": { From 570635ea02a4824b68d8e98de3ed4c26cb7ae8e7 Mon Sep 17 00:00:00 2001 From: mingjun97 Date: Tue, 10 May 2022 23:23:58 -0700 Subject: [PATCH 098/312] Revise version format --- src/main/java/emu/grasscutter/Grasscutter.java | 2 +- src/main/resources/languages/en-US.json | 2 +- src/main/resources/languages/pl-PL.json | 2 +- src/main/resources/languages/zh-CN.json | 2 +- src/main/resources/languages/zh-TW.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index b328a453b..6ca78dfa9 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -84,7 +84,7 @@ public final class Grasscutter { Tools.createGachaMapping(DATA("gacha_mappings.js")); exitEarly = true; } case "-version" -> { - System.out.println("Grasscutter version: " + BuildConfig.VERSION + "\nGit Hash: " + BuildConfig.GIT_HASH); exitEarly = true; + System.out.println("Grasscutter version: " + BuildConfig.VERSION + "-" + BuildConfig.GIT_HASH); exitEarly = true; } } } diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 099e36eaa..81e97eed7 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -49,7 +49,7 @@ "run_mode_help": "Server run mode must be 'HYBRID', 'DISPATCH_ONLY', or 'GAME_ONLY'. Unable to start Grasscutter...", "create_resources": "Creating resources folder...", "resources_error": "Place a copy of 'BinOutput' and 'ExcelBinOutput' in the resources folder.", - "version": "Grasscutter version: %s, Git Hash: %s" + "version": "Grasscutter version: %s-%s" } }, "commands": { diff --git a/src/main/resources/languages/pl-PL.json b/src/main/resources/languages/pl-PL.json index 0f5d88aa9..aa06723d8 100644 --- a/src/main/resources/languages/pl-PL.json +++ b/src/main/resources/languages/pl-PL.json @@ -46,7 +46,7 @@ "run_mode_help": "Tryb pracy serwera musi być ustawiony na 'HYBRID', 'DISPATCH_ONLY', lub 'GAME_ONLY'. Nie można wystartować Grasscutter...", "create_resources": "Tworzenie folderu resources...", "resources_error": "Umieść kopię 'BinOutput' i 'ExcelBinOutput' w folderze resources.", - "version": "Grasscutter versión: %s, Git Hash: %s" + "version": "Grasscutter versión: %s-%s" } }, "commands": { diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 5f2b02736..84d4a8c94 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -46,7 +46,7 @@ "run_mode_help": "服务器运行模式必须为 HYBRID、DISPATCH_ONLY 或 GAME_ONLY。Grasscutter 启动失败...", "create_resources": "正在创建 resources 目录...", "resources_error": "请将 BinOutput 和 ExcelBinOutput 复制到 resources 目录。", - "version": "Grasscutter版本: %s, Git Hash: %s" + "version": "Grasscutter版本: %s-%s" } }, "commands": { diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index 3cb850415..8e7a75949 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -46,7 +46,7 @@ "run_mode_help": "伺服器運行模式必須為 HYBRID 或者 DISPATCH_ONLY 或者 GAME_ONLY。Grasscutter 啟動失敗...", "create_resources": "正在建立 resources 資料夾...", "resources_error": "請將 BinOutput 和 ExcelBinOutput 複製到 resources 資料夾。", - "version": "Grasscutter版本: %s, Git Hash: %s" + "version": "Grasscutter版本: %s-%s" } }, "commands": { From 41de6bd229c0329da9a12ab8570266b4b879bd4a Mon Sep 17 00:00:00 2001 From: mingjun97 Date: Tue, 10 May 2022 23:34:53 -0700 Subject: [PATCH 099/312] Make `injectGitHash` as a task --- build.gradle | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index 47f433b57..ffb6a498e 100644 --- a/build.gradle +++ b/build.gradle @@ -45,13 +45,6 @@ targetCompatibility = JavaVersion.VERSION_17 group = 'xyz.grasscutters' version = '1.1.1-dev' -def gitCommitHash = { - try { - return 'git rev-parse --verify --short HEAD'.execute().text.trim() - } catch (e) { - return "GIT_NOT_FOUND" - } -} sourceCompatibility = 17 targetCompatibility = 17 @@ -122,13 +115,6 @@ jar { from('src/main/java') { include '*.xml' } - new File(projectDir, "src/main/java/emu/grasscutter/BuildConfig.java").text = """ - package emu.grasscutter; - public class BuildConfig { - public static final String VERSION = \"${version}\"; - public static final String GIT_HASH = \"${gitCommitHash()}\"; - } - """ destinationDir = file(".") } @@ -242,6 +228,26 @@ javadoc { } } +task injectGitHash { + doLast { + def gitCommitHash = { + try { + return 'git rev-parse --verify --short HEAD'.execute().text.trim() + } catch (e) { + return "GIT_NOT_FOUND" + } + } + new File(projectDir, "src/main/java/emu/grasscutter/BuildConfig.java").text = """ + package emu.grasscutter; + public class BuildConfig { + public static final String VERSION = \"${version}\"; + public static final String GIT_HASH = \"${gitCommitHash()}\"; + } + """ + } +} + processResources { dependsOn "generateProto" + dependsOn "injectGitHash" } From c105c71e533cbdf30bc6642c036d9f590ab6cb49 Mon Sep 17 00:00:00 2001 From: mingjun97 Date: Tue, 10 May 2022 23:55:40 -0700 Subject: [PATCH 100/312] Fix building error --- build.gradle | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/build.gradle b/build.gradle index ffb6a498e..3a8d2c34d 100644 --- a/build.gradle +++ b/build.gradle @@ -229,25 +229,22 @@ javadoc { } task injectGitHash { - doLast { - def gitCommitHash = { - try { - return 'git rev-parse --verify --short HEAD'.execute().text.trim() - } catch (e) { - return "GIT_NOT_FOUND" - } + def gitCommitHash = { + try { + return 'git rev-parse --verify --short HEAD'.execute().text.trim() + } catch (e) { + return "GIT_NOT_FOUND" } - new File(projectDir, "src/main/java/emu/grasscutter/BuildConfig.java").text = """ - package emu.grasscutter; - public class BuildConfig { - public static final String VERSION = \"${version}\"; - public static final String GIT_HASH = \"${gitCommitHash()}\"; - } - """ } + new File(projectDir, "src/main/java/emu/grasscutter/BuildConfig.java").text = """ + package emu.grasscutter; + public class BuildConfig { + public static final String VERSION = \"${version}\"; + public static final String GIT_HASH = \"${gitCommitHash()}\"; + } + """ } processResources { dependsOn "generateProto" - dependsOn "injectGitHash" } From 57c7f7a43b3ed20194c1abccdfa662dd0efa3dcd Mon Sep 17 00:00:00 2001 From: ImmuState Date: Wed, 11 May 2022 11:19:25 -0700 Subject: [PATCH 101/312] Add gacha details page. --- data/gacha_details.html | 121 ++++++++++++++++++ .../grasscutter/game/gacha/GachaBanner.java | 9 +- .../grasscutter/game/gacha/GachaManager.java | 18 ++- .../server/dispatch/DispatchServer.java | 4 + .../dispatch/http/GachaDetailsHandler.java | 90 +++++++++++++ src/main/resources/languages/en-US.json | 9 ++ src/main/resources/languages/pl-PL.json | 9 ++ src/main/resources/languages/zh-CN.json | 9 ++ src/main/resources/languages/zh-TW.json | 9 ++ 9 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 data/gacha_details.html create mode 100644 src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java diff --git a/data/gacha_details.html b/data/gacha_details.html new file mode 100644 index 000000000..16cf7313a --- /dev/null +++ b/data/gacha_details.html @@ -0,0 +1,121 @@ + + + + + + + + + Banner Details + + + +
+
+

{{TITLE}}

+ +

{{AVAILABLE_FIVE_STARS}}

+
+
    +
+ +

{{AVAILABLE_FOUR_STARS}}

+
+
    +
+ +

{{AVAILABLE_THREE_STARS}}

+
+
    +
+
+
+
+ +
+ + + + diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java index dce433fcf..7a2646a4f 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -102,6 +102,11 @@ public class GachaBanner { + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + "/gacha?s=" + sessionKey + "&gachaType=" + gachaType; + String details = "http" + (DISPATCH_INFO.encryption.useInRouting ? "s" : "") + "://" + + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" + + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + + "/gacha/details?s=" + sessionKey + "&gachaType=" + gachaType; + // Grasscutter.getLogger().info("record = " + record); GachaInfo.Builder info = GachaInfo.newBuilder() .setGachaType(this.getGachaType()) @@ -112,8 +117,8 @@ public class GachaBanner { .setCostItemNum(1) .setGachaPrefabPath(this.getPrefabPath()) .setGachaPreviewPrefabPath(this.getPreviewPrefabPath()) - .setGachaProbUrl(record) - .setGachaProbUrlOversea(record) + .setGachaProbUrl(details) + .setGachaProbUrlOversea(details) .setGachaRecordUrl(record) .setGachaRecordUrlOversea(record) .setTenCostItemId(this.getCostItem()) diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index 03edca09a..f0baf65a9 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -65,7 +65,23 @@ public class GachaManager { public Int2ObjectMap getGachaBanners() { return gachaBanners; } - + + public int[] getYellowAvatars() { + return this.yellowAvatars; + } + public int[] getYellowWeapons() { + return this.yellowWeapons; + } + public int[] getPurpleAvatars() { + return this.purpleAvatars; + } + public int[] getPurpleWeapons() { + return this.purpleWeapons; + } + public int[] getBlueWeapons() { + return this.blueWeapons; + } + public int randomRange(int min, int max) { return ThreadLocalRandom.current().nextInt(max - min + 1) + min; } diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index 4e09f8881..c78aff7c6 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -16,6 +16,7 @@ import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo; import emu.grasscutter.server.dispatch.authentication.AuthenticationHandler; import emu.grasscutter.server.dispatch.authentication.DefaultAuthenticationHandler; +import emu.grasscutter.server.dispatch.http.GachaDetailsHandler; import emu.grasscutter.server.dispatch.http.GachaRecordHandler; import emu.grasscutter.server.dispatch.json.*; import emu.grasscutter.server.dispatch.json.ComboTokenReqJson.LoginTokenData; @@ -455,6 +456,9 @@ public final class DispatchServer { httpServer.raw().config.addSinglePageRoot("/gacha/mappings", gachaMappingsPath, Location.EXTERNAL); + // gacha details + httpServer.get("/gacha/details", new GachaDetailsHandler()); + // static file support for plugins httpServer.raw().config.precompressStaticFiles = false; // If this isn't set to false, files such as images may appear corrupted when serving static files diff --git a/src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java b/src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java new file mode 100644 index 000000000..d46cead40 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java @@ -0,0 +1,90 @@ +package emu.grasscutter.server.dispatch.http; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.Account; +import emu.grasscutter.game.gacha.GachaBanner; +import emu.grasscutter.game.gacha.GachaManager; +import emu.grasscutter.game.gacha.GachaBanner.BannerType; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.Utils; +import express.http.HttpContextHandler; +import express.http.Request; +import express.http.Response; + +import static emu.grasscutter.utils.Language.translate; + +import static emu.grasscutter.Configuration.*; + +public final class GachaDetailsHandler implements HttpContextHandler { + private final String render_template; + + public GachaDetailsHandler() { + File template = new File(Utils.toFilePath(DATA("/gacha_details.html"))); + this.render_template = template.exists() ? new String(FileUtils.read(template)) : null; + } + + @Override + public void handle(Request req, Response res) throws IOException { + String response = this.render_template; + + // Get player info (for langauge). + String sessionKey = req.query("s"); + Account account = DatabaseHelper.getAccountBySessionKey(sessionKey); + Player player = Grasscutter.getGameServer().getPlayerByUid(account.getPlayerUid()); + + // If the template was not loaded, return an error. + if (this.render_template == null) { + res.send(translate(player, "gacha.details.template_missing")); + return; + } + + // Add translated title etc. to the page. + response = response.replace("{{TITLE}}", translate(player, "gacha.details.title")); + response = response.replace("{{AVAILABLE_FIVE_STARS}}", translate(player, "gacha.details.available_five_stars")); + response = response.replace("{{AVAILABLE_FOUR_STARS}}", translate(player, "gacha.details.available_four_stars")); + response = response.replace("{{AVAILABLE_THREE_STARS}}", translate(player, "gacha.details.available_three_stars")); + + // Get the banner info for the banner we want. + int gachaType = Integer.parseInt(req.query("gachaType")); + GachaManager manager = Grasscutter.getGameServer().getGachaManager(); + GachaBanner banner = manager.getGachaBanners().get(gachaType); + + // Add 5-star items. + Set fiveStarItems = new LinkedHashSet<>(); + + Arrays.stream(banner.getRateUpItems1()).forEach(i -> fiveStarItems.add(Integer.toString(i))); + if (banner.getBannerType() == BannerType.STANDARD || banner.getBannerType() == BannerType.EVENT) { + Arrays.stream(manager.getYellowAvatars()).forEach(i -> fiveStarItems.add(Integer.toString(i))); + } + if (banner.getBannerType() == BannerType.STANDARD || banner.getBannerType() == BannerType.WEAPON) { + Arrays.stream(manager.getYellowWeapons()).forEach(i -> fiveStarItems.add(Integer.toString(i))); + } + + response = response.replace("{{FIVE_STARS}}", "[" + String.join(",", fiveStarItems) + "]"); + + // Add 4-star items. + Set fourStarItems = new LinkedHashSet<>(); + + Arrays.stream(banner.getRateUpItems2()).forEach(i -> fourStarItems.add(Integer.toString(i))); + Arrays.stream(manager.getPurpleAvatars()).forEach(i -> fourStarItems.add(Integer.toString(i))); + Arrays.stream(manager.getPurpleWeapons()).forEach(i -> fourStarItems.add(Integer.toString(i))); + + response = response.replace("{{FOUR_STARS}}", "[" + String.join(",", fourStarItems) + "]"); + + // Add 3-star items. + Set threeStarItems = new LinkedHashSet<>(); + Arrays.stream(manager.getBlueWeapons()).forEach(i -> threeStarItems.add(Integer.toString(i))); + response = response.replace("{{THREE_STARS}}", "[" + String.join(",", threeStarItems) + "]"); + + // Done. + res.send(response); + } +} diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 81e97eed7..42b52d7f5 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -352,5 +352,14 @@ "resetshop": { "description": "reset shop" } + }, + "gacha": { + "details": { + "title": "Banner Details", + "available_five_stars": "Available 5-star Items", + "available_four_stars": "Available 4-star Items", + "available_three_stars": "Available 3-star Items", + "template_missing": "data/gacha_details.html is missing." + } } } diff --git a/src/main/resources/languages/pl-PL.json b/src/main/resources/languages/pl-PL.json index aa06723d8..e5eff2d84 100644 --- a/src/main/resources/languages/pl-PL.json +++ b/src/main/resources/languages/pl-PL.json @@ -302,5 +302,14 @@ "resetshop": { "description": "zresetuj sklep" } + }, + "gacha": { + "details": { + "title": "Banner Details", + "available_five_stars": "Available 5-star Items", + "available_four_stars": "Available 4-star Items", + "available_three_stars": "Available 3-star Items", + "template_missing": "data/gacha_details.html is missing." + } } } \ No newline at end of file diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 84d4a8c94..ac46370d5 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -349,5 +349,14 @@ "resetshop": { "description": "重置商店刷新时间" } + }, + "gacha": { + "details": { + "title": "Banner Details", + "available_five_stars": "Available 5-star Items", + "available_four_stars": "Available 4-star Items", + "available_three_stars": "Available 3-star Items", + "template_missing": "data/gacha_details.html is missing." + } } } diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index 8e7a75949..f6ae52c9d 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -302,5 +302,14 @@ "resetshop": { "description": "重置商店時間" } + }, + "gacha": { + "details": { + "title": "Banner Details", + "available_five_stars": "Available 5-star Items", + "available_four_stars": "Available 4-star Items", + "available_three_stars": "Available 3-star Items", + "template_missing": "data/gacha_details.html is missing." + } } } From e5a85f81c27d564d3ddefb960e44a8c8c7a1478a Mon Sep 17 00:00:00 2001 From: ImmuState Date: Wed, 11 May 2022 11:36:14 -0700 Subject: [PATCH 102/312] Insert language setting based on the player's account. --- data/gacha_details.html | 10 +++++----- .../server/dispatch/http/GachaDetailsHandler.java | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/data/gacha_details.html b/data/gacha_details.html index 16cf7313a..ccd775ef6 100644 --- a/data/gacha_details.html +++ b/data/gacha_details.html @@ -38,7 +38,7 @@

{{TITLE}}

- +

{{AVAILABLE_FIVE_STARS}}


    @@ -81,11 +81,11 @@ @@ -161,32 +128,12 @@ } return "" + itemID + ""; } - function dateFormatter(timeStamp) { - var date = new Date(timeStamp); - if (lang == "en-us" || lang == null) { // MM/DD/YYYY hh:mm:ss.SSS - return String(date.getMonth()+1).padStart(2, "0") + - "/"+String(date.getDate()).padStart(2, "0")+ - "/"+date.getFullYear()+ - " "+String(date.getHours()).padStart(2, "0")+ - ":"+String(date.getMinutes()).padStart(2, "0")+ - ":"+String(date.getSeconds()).padStart(2, "0")+ - "."+String(date.getMilliseconds()).padStart(3, "0"); - } else if (lang == "zh-cn") { // YYYY/MM/DD hh:mm:ss.SSS - return date.getFullYear()+ - "/" + String(date.getMonth()+1).padStart(2, "0") + - "/"+String(date.getDate()).padStart(2, "0")+ - " "+String(date.getHours()).padStart(2, "0")+ - ":"+String(date.getMinutes()).padStart(2, "0")+ - ":"+String(date.getSeconds()).padStart(2, "0")+ - "."+String(date.getMilliseconds()).padStart(3, "0"); - } - } (function (){ var container = document.getElementById("container"); record.forEach(element => { var e = document.createElement("tr"); - e.innerHTML= "" + dateFormatter(element.time) + "" + itemMapper(element.item) + ""; + e.innerHTML= "" + (new Date(element.time).toLocaleString(lang)) + "" + itemMapper(element.item) + ""; container.appendChild(e); }); // setup pagenation buttons From 3c55aa64eb3880aace707bc58fff083870fac4d0 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Fri, 13 May 2022 14:59:05 +0800 Subject: [PATCH 123/312] fix: LEAK: ByteBuf.release() was not called --- src/main/java/emu/grasscutter/server/game/GameSession.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/emu/grasscutter/server/game/GameSession.java b/src/main/java/emu/grasscutter/server/game/GameSession.java index 7cc9a799f..cf6386770 100644 --- a/src/main/java/emu/grasscutter/server/game/GameSession.java +++ b/src/main/java/emu/grasscutter/server/game/GameSession.java @@ -252,6 +252,7 @@ public class GameSession extends KcpChannel { } catch (Exception e) { e.printStackTrace(); } finally { + data.release(); packet.release(); } } From 631a53030c5d86242aa6214aa3a01b4c2a19eb4e Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 13 May 2022 03:12:25 -0700 Subject: [PATCH 124/312] Switch to using quest excels --- .../java/emu/grasscutter/data/GameData.java | 13 +- .../emu/grasscutter/data/ResourceLoader.java | 17 +-- .../data/custom/MainQuestData.java | 53 ++++++++ .../grasscutter/data/custom/QuestConfig.java | 25 ---- .../data/custom/QuestConfigData.java | 104 ---------------- .../emu/grasscutter/data/def/QuestData.java | 115 ++++++++++++++++++ .../grasscutter/game/quest/GameMainQuest.java | 1 - .../emu/grasscutter/game/quest/GameQuest.java | 49 +++++--- .../grasscutter/game/quest/QuestManager.java | 27 ++-- .../grasscutter/game/quest/QuestValue.java | 4 +- .../game/quest/ServerQuestHandler.java | 12 +- .../game/quest/conditions/BaseCondition.java | 6 +- .../ConditionPlayerLevelEqualGreater.java | 8 +- .../quest/conditions/ConditionStateEqual.java | 10 +- .../game/quest/content/BaseContent.java | 6 +- .../quest/content/ContentCompleteTalk.java | 6 +- ...uestTriggerType.java => QuestTrigger.java} | 4 +- .../game/quest/handlers/QuestBaseHandler.java | 2 +- .../server/packet/recv/HandlerNpcTalkReq.java | 4 +- ...etServerCondMeetQuestListUpdateNotify.java | 3 - .../java/emu/grasscutter/tools/Tools.java | 12 +- 21 files changed, 262 insertions(+), 219 deletions(-) create mode 100644 src/main/java/emu/grasscutter/data/custom/MainQuestData.java delete mode 100644 src/main/java/emu/grasscutter/data/custom/QuestConfig.java delete mode 100644 src/main/java/emu/grasscutter/data/custom/QuestConfigData.java create mode 100644 src/main/java/emu/grasscutter/data/def/QuestData.java rename src/main/java/emu/grasscutter/game/quest/enums/{QuestTriggerType.java => QuestTrigger.java} (99%) diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 2b40818e1..75b840202 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -12,7 +12,7 @@ import emu.grasscutter.data.custom.AbilityEmbryoEntry; import emu.grasscutter.data.custom.AbilityModifier; import emu.grasscutter.data.custom.AbilityModifierEntry; import emu.grasscutter.data.custom.OpenConfigEntry; -import emu.grasscutter.data.custom.QuestConfig; +import emu.grasscutter.data.custom.MainQuestData; import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.data.def.*; import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; @@ -28,7 +28,7 @@ public class GameData { private static final Map abilityModifiers = new HashMap<>(); private static final Map openConfigEntries = new HashMap<>(); private static final Map scenePointEntries = new HashMap<>(); - private static final Int2ObjectMap questConfigs = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap mainQuestData = new Int2ObjectOpenHashMap<>(); // ExcelConfigs private static final Int2ObjectMap playerLevelDataMap = new Int2ObjectOpenHashMap<>(); @@ -70,6 +70,7 @@ public class GameData { private static final Int2ObjectMap worldLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap dailyDungeonDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap dungeonDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap questDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap shopGoodsDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap combineDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap rewardPreviewDataMap = new Int2ObjectOpenHashMap<>(); @@ -124,8 +125,8 @@ public class GameData { return getScenePointEntries().get(sceneId + "_" + pointId); } - public static Int2ObjectMap getQuestConfigs() { - return questConfigs; + public static Int2ObjectMap getMainQuestDataMap() { + return mainQuestData; } public static Int2ObjectMap getAvatarDataMap() { @@ -337,4 +338,8 @@ public class GameData { public static Int2ObjectMap getTowerScheduleDataMap(){ return towerScheduleDataMap; } + + public static Int2ObjectMap getQuestDataMap() { + return questDataMap; + } } diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index 5c2ac1ee6..4b940c44d 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -24,9 +24,7 @@ import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction; import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierActionType; import emu.grasscutter.data.custom.AbilityModifierEntry; import emu.grasscutter.data.custom.OpenConfigEntry; -import emu.grasscutter.data.custom.QuestConfig; -import emu.grasscutter.data.custom.QuestConfigData; -import emu.grasscutter.data.custom.QuestConfigData.SubQuestConfigData; +import emu.grasscutter.data.custom.MainQuestData; import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.game.world.SpawnDataEntry.*; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -407,24 +405,19 @@ public class ResourceLoader { } for (File file : folder.listFiles()) { - QuestConfigData mainQuest = null; + MainQuestData mainQuest = null; try (FileReader fileReader = new FileReader(file)) { - mainQuest = Grasscutter.getGsonFactory().fromJson(fileReader, QuestConfigData.class); + mainQuest = Grasscutter.getGsonFactory().fromJson(fileReader, MainQuestData.class); } catch (Exception e) { e.printStackTrace(); continue; } - if (mainQuest.getSubQuests() != null) { - for (SubQuestConfigData subQuest : mainQuest.getSubQuests()) { - QuestConfig quest = new QuestConfig(mainQuest, subQuest); - GameData.getQuestConfigs().put(quest.getId(), quest); - } - } + GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest); } - Grasscutter.getLogger().info("Loaded " + GameData.getQuestConfigs().size() + " Quest Configs"); + Grasscutter.getLogger().info("Loaded " + GameData.getMainQuestDataMap().size() + " MainQuestDatas."); } // BinOutput configs diff --git a/src/main/java/emu/grasscutter/data/custom/MainQuestData.java b/src/main/java/emu/grasscutter/data/custom/MainQuestData.java new file mode 100644 index 000000000..e405e3598 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/custom/MainQuestData.java @@ -0,0 +1,53 @@ +package emu.grasscutter.data.custom; + +import emu.grasscutter.game.quest.enums.LogicType; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.enums.QuestType; + +public class MainQuestData { + private int id; + private int series; + private QuestType type; + + private long titleTextMapHash; + private int[] suggestTrackMainQuestList; + private int[] rewardIdList; + + private SubQuestData[] subQuests; + + public int getId() { + return id; + } + + public int getSeries() { + return series; + } + + public QuestType getType() { + return type; + } + + public long getTitleTextMapHash() { + return titleTextMapHash; + } + + public int[] getSuggestTrackMainQuestList() { + return suggestTrackMainQuestList; + } + + public int[] getRewardIdList() { + return rewardIdList; + } + + public SubQuestData[] getSubQuests() { + return subQuests; + } + + public static class SubQuestData { + private int subId; + + public int getSubId() { + return subId; + } + } +} diff --git a/src/main/java/emu/grasscutter/data/custom/QuestConfig.java b/src/main/java/emu/grasscutter/data/custom/QuestConfig.java deleted file mode 100644 index 8674ff7ab..000000000 --- a/src/main/java/emu/grasscutter/data/custom/QuestConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package emu.grasscutter.data.custom; - -import emu.grasscutter.data.custom.QuestConfigData.SubQuestConfigData; - -public class QuestConfig { - private final QuestConfigData mainQuest; - private final SubQuestConfigData subQuest; - - public QuestConfig(QuestConfigData mainQuest, SubQuestConfigData subQuest) { - this.mainQuest = mainQuest; - this.subQuest = subQuest; - } - - public int getId() { - return subQuest.getSubId(); - } - - public QuestConfigData getMainQuest() { - return mainQuest; - } - - public SubQuestConfigData getSubQuest() { - return subQuest; - } -} diff --git a/src/main/java/emu/grasscutter/data/custom/QuestConfigData.java b/src/main/java/emu/grasscutter/data/custom/QuestConfigData.java deleted file mode 100644 index 3ede024f2..000000000 --- a/src/main/java/emu/grasscutter/data/custom/QuestConfigData.java +++ /dev/null @@ -1,104 +0,0 @@ -package emu.grasscutter.data.custom; - -import emu.grasscutter.game.quest.enums.LogicType; -import emu.grasscutter.game.quest.enums.QuestTriggerType; -import emu.grasscutter.game.quest.enums.QuestType; - -public class QuestConfigData { - private int id; - private int series; - private QuestType type; - - private long titleTextMapHash; - private int[] suggestTrackMainQuestList; - private int[] rewardIdList; - - private SubQuestConfigData[] subQuests; - - public int getId() { - return id; - } - - public int getSeries() { - return series; - } - - public QuestType getType() { - return type; - } - - public long getTitleTextMapHash() { - return titleTextMapHash; - } - - public int[] getSuggestTrackMainQuestList() { - return suggestTrackMainQuestList; - } - - public int[] getRewardIdList() { - return rewardIdList; - } - - public SubQuestConfigData[] getSubQuests() { - return subQuests; - } - - public class SubQuestConfigData { - private int subId; - private int mainId; - - private LogicType acceptCondComb; - private QuestCondition[] acceptCond; - - private LogicType finishCondComb; - private QuestCondition[] finishCond; - - private LogicType failCondComb; - private QuestCondition[] failCond; - - public int getSubId() { - return subId; - } - - public int getMainId() { - return mainId; - } - - public LogicType getAcceptCondComb() { - return acceptCondComb; - } - - public QuestCondition[] getAcceptCond() { - return acceptCond; - } - - public LogicType getFinishCondComb() { - return finishCondComb; - } - - public QuestCondition[] getFinishCond() { - return finishCond; - } - - public LogicType getFailCondComb() { - return failCondComb; - } - - public QuestCondition[] getFailCond() { - return failCond; - } - } - - public class QuestCondition { - private QuestTriggerType type; - private int[] param; - - public QuestTriggerType getType() { - return type; - } - - public int[] getParam() { - return param; - } - } -} diff --git a/src/main/java/emu/grasscutter/data/def/QuestData.java b/src/main/java/emu/grasscutter/data/def/QuestData.java new file mode 100644 index 000000000..31ac2ce7e --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/QuestData.java @@ -0,0 +1,115 @@ +package emu.grasscutter.data.def; + +import java.util.Arrays; +import java.util.List; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; +import emu.grasscutter.game.quest.enums.LogicType; +import emu.grasscutter.game.quest.enums.QuestTrigger; + +@ResourceType(name = "QuestExcelConfigData.json") +public class QuestData extends GameResource { + private int SubId; + private int MainId; + private int Order; + private long DescTextMapHash; + + private LogicType AcceptCondComb; + private QuestCondition[] acceptConditons; + private LogicType FinishCondComb; + private QuestCondition[] finishConditons; + private LogicType FailCondComb; + private QuestCondition[] failConditons; + + private List AcceptCond; + private List FinishCond; + private List FailCond; + private List BeginExec; + private List FinishExec; + private List FailExec; + + public int getId() { + return SubId; + } + + public int getMainId() { + return MainId; + } + + public int getOrder() { + return Order; + } + + public long getDescTextMapHash() { + return DescTextMapHash; + } + + public LogicType getAcceptCondComb() { + return AcceptCondComb; + } + + public QuestCondition[] getAcceptCond() { + return acceptConditons; + } + + public LogicType getFinishCondComb() { + return FinishCondComb; + } + + public QuestCondition[] getFinishCond() { + return finishConditons; + } + + public LogicType getFailCondComb() { + return FailCondComb; + } + + public QuestCondition[] getFailCond() { + return failConditons; + } + + public void onLoad() { + this.acceptConditons = AcceptCond.stream().filter(p -> p.Type != null).map(QuestCondition::new).toArray(QuestCondition[]::new); + AcceptCond = null; + this.finishConditons = FinishCond.stream().filter(p -> p.Type != null).map(QuestCondition::new).toArray(QuestCondition[]::new); + FinishCond = null; + this.failConditons = FailCond.stream().filter(p -> p.Type != null).map(QuestCondition::new).toArray(QuestCondition[]::new); + FailCond = null; + } + + public class QuestParam { + QuestTrigger Type; + int[] Param; + String count; + } + + public class QuestExecParam { + QuestTrigger Type; + String[] Param; + String count; + } + + public static class QuestCondition { + private QuestTrigger type; + private int[] param; + private String count; + + public QuestCondition(QuestParam param) { + this.type = param.Type; + this.param = param.Param; + } + + public QuestTrigger getType() { + return type; + } + + public int[] getParam() { + return param; + } + + public String getCount() { + return count; + } + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java index 1ceda3356..bf88b8efe 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java @@ -10,7 +10,6 @@ import dev.morphia.annotations.Id; import dev.morphia.annotations.Indexed; import dev.morphia.annotations.Transient; import emu.grasscutter.data.GameData; -import emu.grasscutter.data.custom.QuestConfig; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.quest.enums.ParentQuestState; diff --git a/src/main/java/emu/grasscutter/game/quest/GameQuest.java b/src/main/java/emu/grasscutter/game/quest/GameQuest.java index d3a240a07..b242166eb 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameQuest.java @@ -2,9 +2,11 @@ package emu.grasscutter.game.quest; import dev.morphia.annotations.Entity; import dev.morphia.annotations.Transient; -import emu.grasscutter.data.custom.QuestConfig; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; -import emu.grasscutter.data.custom.QuestConfigData.SubQuestConfigData; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.custom.MainQuestData; +import emu.grasscutter.data.custom.MainQuestData.SubQuestData; +import emu.grasscutter.data.def.QuestData; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.quest.enums.LogicType; import emu.grasscutter.game.quest.enums.QuestState; @@ -16,7 +18,7 @@ import emu.grasscutter.utils.Utils; @Entity public class GameQuest { @Transient private GameMainQuest mainQuest; - @Transient private QuestConfig config; + @Transient private QuestData questData; private int questId; private int mainQuestId; @@ -32,21 +34,21 @@ public class GameQuest { @Deprecated // Morphia only. Do not use. public GameQuest() {} - public GameQuest(GameMainQuest mainQuest, QuestConfig config) { + public GameQuest(GameMainQuest mainQuest, QuestData questData) { this.mainQuest = mainQuest; - this.questId = config.getId(); - this.mainQuestId = config.getMainQuest().getId(); - this.config = config; + this.questId = questData.getId(); + this.mainQuestId = questData.getMainId(); + this.questData = questData; this.acceptTime = Utils.getCurrentSeconds(); this.startTime = this.acceptTime; this.state = QuestState.QUEST_STATE_UNFINISHED; - if (config.getSubQuest().getFinishCond() != null) { - this.finishProgressList = new int[config.getSubQuest().getFinishCond().length]; + if (questData.getFinishCond()!= null) { + this.finishProgressList = new int[questData.getFinishCond().length]; } - if (config.getSubQuest().getFailCond() != null) { - this.failProgressList = new int[config.getSubQuest().getFailCond().length]; + if (questData.getFailCond() != null) { + this.failProgressList = new int[questData.getFailCond().length]; } this.mainQuest.getChildQuests().put(this.questId, this); @@ -72,13 +74,13 @@ public class GameQuest { return mainQuestId; } - public QuestConfig getConfig() { - return config; + public QuestData getData() { + return questData; } - public void setConfig(QuestConfig config) { + public void setConfig(QuestData config) { if (this.getQuestId() != config.getId()) return; - this.config = config; + this.questData = config; } public QuestState getState() { @@ -148,16 +150,23 @@ public class GameQuest { public boolean tryAcceptQuestLine() { try { - for (SubQuestConfigData questData : getConfig().getMainQuest().getSubQuests()) { - GameQuest quest = getMainQuest().getChildQuestById(questData.getSubId()); + MainQuestData questConfig = GameData.getMainQuestDataMap().get(this.getMainQuestId()); + for (SubQuestData subQuest : questConfig.getSubQuests()) { + GameQuest quest = getMainQuest().getChildQuestById(subQuest.getSubId()); if (quest == null) { + QuestData questData = GameData.getQuestDataMap().get(subQuest.getSubId()); + + if (questData == null) { + continue; + } + int[] accept = new int[questData.getAcceptCond().length]; // TODO for (int i = 0; i < questData.getAcceptCond().length; i++) { QuestCondition condition = questData.getAcceptCond()[i]; - boolean result = getOwner().getServer().getQuestHandler().triggerCondition(this, condition); + boolean result = getOwner().getServer().getQuestHandler().triggerCondition(this, condition, condition.getParam()); accept[i] = result ? 1 : 0; } @@ -165,7 +174,7 @@ public class GameQuest { boolean shouldAccept = LogicType.calculate(questData.getAcceptCondComb(), accept); if (shouldAccept) { - this.getOwner().getQuestManager().addQuest(questData.getSubId()); + this.getOwner().getQuestManager().addQuest(questData.getId()); } } } diff --git a/src/main/java/emu/grasscutter/game/quest/QuestManager.java b/src/main/java/emu/grasscutter/game/quest/QuestManager.java index 76c098b07..548e8241a 100644 --- a/src/main/java/emu/grasscutter/game/quest/QuestManager.java +++ b/src/main/java/emu/grasscutter/game/quest/QuestManager.java @@ -9,12 +9,11 @@ import java.util.function.Consumer; import java.util.function.Function; import emu.grasscutter.data.GameData; -import emu.grasscutter.data.custom.QuestConfig; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; -import emu.grasscutter.data.custom.QuestConfigData.SubQuestConfigData; +import emu.grasscutter.data.def.QuestData; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.game.quest.enums.LogicType; import emu.grasscutter.game.quest.enums.QuestState; import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify; @@ -46,12 +45,12 @@ public class QuestManager { } public GameQuest getQuestById(int questId) { - QuestConfig questConfig = GameData.getQuestConfigs().get(questId); + QuestData questConfig = GameData.getQuestDataMap().get(questId); if (questConfig == null) { return null; } - GameMainQuest mainQuest = getQuests().get(questConfig.getMainQuest().getId()); + GameMainQuest mainQuest = getQuests().get(questConfig.getMainId()); if (mainQuest == null) { return null; @@ -79,8 +78,8 @@ public class QuestManager { } } - public GameMainQuest addMainQuest(QuestConfig questConfig) { - GameMainQuest mainQuest = new GameMainQuest(getPlayer(), questConfig.getMainQuest().getId()); + public GameMainQuest addMainQuest(QuestData questConfig) { + GameMainQuest mainQuest = new GameMainQuest(getPlayer(), questConfig.getMainId()); getQuests().put(mainQuest.getParentQuestId(), mainQuest); getPlayer().sendPacket(new PacketFinishedParentQuestUpdateNotify(mainQuest)); @@ -89,13 +88,13 @@ public class QuestManager { } public GameQuest addQuest(int questId) { - QuestConfig questConfig = GameData.getQuestConfigs().get(questId); + QuestData questConfig = GameData.getQuestDataMap().get(questId); if (questConfig == null) { return null; } // Main quest - GameMainQuest mainQuest = this.getMainQuestById(questConfig.getMainQuest().getId()); + GameMainQuest mainQuest = this.getMainQuestById(questConfig.getMainId()); // Create main quest if it doesnt exist if (mainQuest == null) { @@ -122,11 +121,11 @@ public class QuestManager { return quest; } - public void triggerEvent(QuestTriggerType condType, int... params) { + public void triggerEvent(QuestTrigger condType, int... params) { Set changedQuests = new HashSet<>(); this.forEachActiveQuest(quest -> { - SubQuestConfigData data = quest.getConfig().getSubQuest(); + QuestData data = quest.getData(); for (int i = 0; i < data.getFinishCond().length; i++) { if (quest.getFinishProgressList()[i] == 1) { @@ -150,7 +149,7 @@ public class QuestManager { }); for (GameQuest quest : changedQuests) { - LogicType logicType = quest.getConfig().getSubQuest().getFailCondComb(); + LogicType logicType = quest.getData().getFailCondComb(); int[] progress = quest.getFinishProgressList(); // Handle logical comb @@ -174,7 +173,7 @@ public class QuestManager { for (GameQuest quest : mainQuest.getChildQuests().values()) { quest.setMainQuest(mainQuest); - quest.setConfig(GameData.getQuestConfigs().get(quest.getQuestId())); + quest.setConfig(GameData.getQuestDataMap().get(quest.getQuestId())); } this.getQuests().put(mainQuest.getParentQuestId(), mainQuest); diff --git a/src/main/java/emu/grasscutter/game/quest/QuestValue.java b/src/main/java/emu/grasscutter/game/quest/QuestValue.java index 3042ad5de..42b868fc8 100644 --- a/src/main/java/emu/grasscutter/game/quest/QuestValue.java +++ b/src/main/java/emu/grasscutter/game/quest/QuestValue.java @@ -3,9 +3,9 @@ package emu.grasscutter.game.quest; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.game.quest.enums.QuestTrigger; @Retention(RetentionPolicy.RUNTIME) public @interface QuestValue { - QuestTriggerType value(); + QuestTrigger value(); } diff --git a/src/main/java/emu/grasscutter/game/quest/ServerQuestHandler.java b/src/main/java/emu/grasscutter/game/quest/ServerQuestHandler.java index 1c269de90..36c929ab3 100644 --- a/src/main/java/emu/grasscutter/game/quest/ServerQuestHandler.java +++ b/src/main/java/emu/grasscutter/game/quest/ServerQuestHandler.java @@ -4,11 +4,9 @@ import java.util.Set; import org.reflections.Reflections; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.game.quest.handlers.QuestBaseHandler; -import emu.grasscutter.net.packet.Opcodes; -import emu.grasscutter.server.game.GameServer; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -62,7 +60,7 @@ public class ServerQuestHandler { public boolean triggerCondition(GameQuest quest, QuestCondition condition, int... params) { QuestBaseHandler handler = condHandlers.get(condition.getType().getValue()); - if (handler == null || quest.getConfig() == null) { + if (handler == null || quest.getData() == null) { return false; } @@ -72,7 +70,7 @@ public class ServerQuestHandler { public boolean triggerContent(GameQuest quest, QuestCondition condition, int... params) { QuestBaseHandler handler = contHandlers.get(condition.getType().getValue()); - if (handler == null || quest.getConfig() == null) { + if (handler == null || quest.getData() == null) { return false; } @@ -82,7 +80,7 @@ public class ServerQuestHandler { public boolean triggerExec(GameQuest quest, QuestCondition condition, int... params) { QuestBaseHandler handler = execHandlers.get(condition.getType().getValue()); - if (handler == null || quest.getConfig() == null) { + if (handler == null || quest.getData() == null) { return false; } diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/BaseCondition.java b/src/main/java/emu/grasscutter/game/quest/conditions/BaseCondition.java index 903773f0e..d94e60c22 100644 --- a/src/main/java/emu/grasscutter/game/quest/conditions/BaseCondition.java +++ b/src/main/java/emu/grasscutter/game/quest/conditions/BaseCondition.java @@ -1,12 +1,12 @@ package emu.grasscutter.game.quest.conditions; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.game.quest.QuestValue; import emu.grasscutter.game.quest.GameQuest; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.game.quest.handlers.QuestBaseHandler; -@QuestValue(QuestTriggerType.QUEST_CONTENT_NONE) +@QuestValue(QuestTrigger.QUEST_CONTENT_NONE) public class BaseCondition extends QuestBaseHandler { @Override diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPlayerLevelEqualGreater.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPlayerLevelEqualGreater.java index f5df2b13c..3e3db87fb 100644 --- a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPlayerLevelEqualGreater.java +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPlayerLevelEqualGreater.java @@ -1,17 +1,17 @@ package emu.grasscutter.game.quest.conditions; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.game.quest.QuestValue; import emu.grasscutter.game.quest.GameQuest; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.game.quest.handlers.QuestBaseHandler; -@QuestValue(QuestTriggerType.QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER) +@QuestValue(QuestTrigger.QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER) public class ConditionPlayerLevelEqualGreater extends QuestBaseHandler { @Override public boolean execute(GameQuest quest, QuestCondition condition, int... params) { - return quest.getOwner().getLevel() >= condition.getParam()[0]; + return quest.getOwner().getLevel() >= params[0]; } } diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java index 71b44c967..37ecc6d30 100644 --- a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java @@ -1,20 +1,20 @@ package emu.grasscutter.game.quest.conditions; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.game.quest.QuestValue; import emu.grasscutter.game.quest.GameQuest; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.game.quest.handlers.QuestBaseHandler; -@QuestValue(QuestTriggerType.QUEST_COND_STATE_EQUAL) +@QuestValue(QuestTrigger.QUEST_COND_STATE_EQUAL) public class ConditionStateEqual extends QuestBaseHandler { @Override public boolean execute(GameQuest quest, QuestCondition condition, int... params) { - GameQuest checkQuest = quest.getOwner().getQuestManager().getQuestById(condition.getParam()[0]); + GameQuest checkQuest = quest.getOwner().getQuestManager().getQuestById(params[0]); if (checkQuest != null) { - return checkQuest.getState().getValue() == condition.getParam()[1]; + return checkQuest.getState().getValue() == params[1]; } return false; diff --git a/src/main/java/emu/grasscutter/game/quest/content/BaseContent.java b/src/main/java/emu/grasscutter/game/quest/content/BaseContent.java index 820d6f133..ce700896d 100644 --- a/src/main/java/emu/grasscutter/game/quest/content/BaseContent.java +++ b/src/main/java/emu/grasscutter/game/quest/content/BaseContent.java @@ -1,12 +1,12 @@ package emu.grasscutter.game.quest.content; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.game.quest.QuestValue; import emu.grasscutter.game.quest.GameQuest; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.game.quest.handlers.QuestBaseHandler; -@QuestValue(QuestTriggerType.QUEST_CONTENT_NONE) +@QuestValue(QuestTrigger.QUEST_CONTENT_NONE) public class BaseContent extends QuestBaseHandler { @Override diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java b/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java index aad196306..3423519ec 100644 --- a/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java @@ -1,12 +1,12 @@ package emu.grasscutter.game.quest.content; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.game.quest.QuestValue; import emu.grasscutter.game.quest.GameQuest; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.game.quest.handlers.QuestBaseHandler; -@QuestValue(QuestTriggerType.QUEST_CONTENT_COMPLETE_TALK) +@QuestValue(QuestTrigger.QUEST_CONTENT_COMPLETE_TALK) public class ContentCompleteTalk extends QuestBaseHandler { @Override diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestTriggerType.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestTrigger.java similarity index 99% rename from src/main/java/emu/grasscutter/game/quest/enums/QuestTriggerType.java rename to src/main/java/emu/grasscutter/game/quest/enums/QuestTrigger.java index cf8dabdba..def3a399d 100644 --- a/src/main/java/emu/grasscutter/game/quest/enums/QuestTriggerType.java +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestTrigger.java @@ -1,6 +1,6 @@ package emu.grasscutter.game.quest.enums; -public enum QuestTriggerType { +public enum QuestTrigger { QUEST_COND_NONE (0), QUEST_COND_STATE_EQUAL (1), QUEST_COND_STATE_NOT_EQUAL (2), @@ -225,7 +225,7 @@ public enum QuestTriggerType { private final int value; - QuestTriggerType(int id) { + QuestTrigger(int id) { this.value = id; } diff --git a/src/main/java/emu/grasscutter/game/quest/handlers/QuestBaseHandler.java b/src/main/java/emu/grasscutter/game/quest/handlers/QuestBaseHandler.java index 68bf88361..5a3514200 100644 --- a/src/main/java/emu/grasscutter/game/quest/handlers/QuestBaseHandler.java +++ b/src/main/java/emu/grasscutter/game/quest/handlers/QuestBaseHandler.java @@ -1,6 +1,6 @@ package emu.grasscutter.game.quest.handlers; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.game.quest.GameQuest; public abstract class QuestBaseHandler { diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java index 515552289..82248c98c 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java @@ -1,7 +1,7 @@ package emu.grasscutter.server.packet.recv; import emu.grasscutter.game.inventory.GameItem; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.NpcTalkReqOuterClass.NpcTalkReq; @@ -16,7 +16,7 @@ public class HandlerNpcTalkReq extends PacketHandler { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { NpcTalkReq req = NpcTalkReq.parseFrom(payload); - session.getPlayer().getQuestManager().triggerEvent(QuestTriggerType.QUEST_CONTENT_COMPLETE_TALK, req.getTalkId()); + session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_COMPLETE_TALK, req.getTalkId()); session.send(new PacketNpcTalkRsp(req.getNpcEntityId(), req.getTalkId(), req.getEntityId())); } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java index b2ea3d577..fa2e8ab81 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java @@ -1,9 +1,6 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.custom.QuestConfig; import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.quest.GameMainQuest; import emu.grasscutter.game.quest.GameQuest; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index 8b28c027e..5b0f563ee 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -19,10 +19,11 @@ import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandMap; import emu.grasscutter.data.GameData; import emu.grasscutter.data.ResourceLoader; -import emu.grasscutter.data.custom.QuestConfig; +import emu.grasscutter.data.custom.MainQuestData; import emu.grasscutter.data.def.AvatarData; import emu.grasscutter.data.def.ItemData; import emu.grasscutter.data.def.MonsterData; +import emu.grasscutter.data.def.QuestData; import emu.grasscutter.data.def.SceneData; import emu.grasscutter.utils.Utils; @@ -149,13 +150,16 @@ final class ToolsWithLanguageOption { writer.println(data.getId() + " : " + data.getScriptData()); } + writer.println(); + writer.println("// Quests"); - list = new ArrayList<>(GameData.getQuestConfigs().keySet()); + list = new ArrayList<>(GameData.getQuestDataMap().keySet()); Collections.sort(list); for (Integer id : list) { - QuestConfig data = GameData.getQuestConfigs().get(id); - writer.println(data.getId() + " : " + map.get(data.getMainQuest().getTitleTextMapHash())); + QuestData data = GameData.getQuestDataMap().get(id); + MainQuestData mainQuest = GameData.getMainQuestDataMap().get(data.getMainId()); + writer.println(data.getId() + " : " + map.get(mainQuest.getTitleTextMapHash()) + " - " + map.get(data.getDescTextMapHash())); } writer.println(); From cbd46e9215fb02ac625697dff84a6c35dbd42812 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 13 May 2022 05:33:43 -0700 Subject: [PATCH 125/312] Add one more quest trigger --- .../game/dungeons/DungeonManager.java | 4 +++- .../game/quest/content/ContentEnterDungeon.java | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/main/java/emu/grasscutter/game/quest/content/ContentEnterDungeon.java diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java index 0a68e6ab0..5c0d1fd27 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java @@ -7,6 +7,7 @@ import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.data.def.DungeonData; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.SceneType; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.server.game.GameServer; @@ -51,8 +52,9 @@ public class DungeonManager { int sceneId = data.getSceneId(); player.getScene().setPrevScene(sceneId); - if(player.getWorld().transferPlayerToScene(player, sceneId, data)){ + if (player.getWorld().transferPlayerToScene(player, sceneId, data)) { player.getScene().addDungeonSettleObserver(basicDungeonSettleObserver); + player.getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_ENTER_DUNGEON, data.getId()); } player.getScene().setPrevScenePoint(pointId); diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentEnterDungeon.java b/src/main/java/emu/grasscutter/game/quest/content/ContentEnterDungeon.java new file mode 100644 index 000000000..e00e59f9a --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentEnterDungeon.java @@ -0,0 +1,17 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.data.def.QuestData.QuestCondition; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_CONTENT_ENTER_DUNGEON) +public class ContentEnterDungeon extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestCondition condition, int... params) { + return condition.getParam()[0] == params[0]; + } + +} From f83f13204ee53bafbf6ad4f6a60b6c0e24379ccc Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Fri, 6 May 2022 23:39:45 +0930 Subject: [PATCH 126/312] Gacha rework Add fallback stripping and C6 stripping Converting banner definitions from pity vars to lerp arrays Properly implement rates and pool smoothing Also move reusable functions to Utils --- data/Banners.json | 19 +- .../grasscutter/game/gacha/GachaBanner.java | 82 +++-- .../grasscutter/game/gacha/GachaManager.java | 314 ++++++++++-------- .../game/gacha/PlayerGachaBannerInfo.java | 84 ++++- .../java/emu/grasscutter/utils/Utils.java | 66 ++++ 5 files changed, 397 insertions(+), 168 deletions(-) diff --git a/data/Banners.json b/data/Banners.json index a4f724ac9..1aaf39cb7 100644 --- a/data/Banners.json +++ b/data/Banners.json @@ -10,8 +10,9 @@ "beginTime": 0, "endTime": 1924992000, "sortId": 1000, - "rateUpItems1": [], - "rateUpItems2": [] + "fallbackItems4Pool1": [1006, 1014, 1015, 1020, 1021, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064], + "weights4": [[1,510], [8,510], [10,10000]], + "weights5": [[1,75], [73,150], [90,10000]] }, { "gachaType": 301, @@ -24,9 +25,10 @@ "beginTime": 0, "endTime": 1924992000, "sortId": 9998, - "maxItemType": 1, - "rateUpItems1": [1002], - "rateUpItems2": [1053, 1020, 1045] + "rateUpItems4": [1053, 1020, 1045], + "rateUpItems5": [1002], + "fallbackItems5Pool2": [], + "weights5": [[1,80], [73,80], [90,10000]] }, { "gachaType": 302, @@ -39,11 +41,12 @@ "beginTime": 0, "endTime": 1924992000, "sortId": 9997, - "minItemType": 2, "eventChance": 75, "softPity": 80, "hardPity": 80, - "rateUpItems1": [11509, 12504], - "rateUpItems2": [11401, 12402, 13407, 14401, 15401] + "rateUpItems4": [11401, 12402, 13407, 14401, 15401], + "rateUpItems5": [11509, 12504], + "fallbackItems5Pool1": [], + "weights5": [[1,100], [62,100], [73, 7800], [80,10000]] } ] diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java index 7a2646a4f..52630768c 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -2,6 +2,7 @@ package emu.grasscutter.game.gacha; import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo; import emu.grasscutter.net.proto.GachaUpInfoOuterClass.GachaUpInfo; +import emu.grasscutter.utils.Utils; import static emu.grasscutter.Configuration.*; @@ -15,14 +16,31 @@ public class GachaBanner { private int beginTime; private int endTime; private int sortId; - private int[] rateUpItems1; - private int[] rateUpItems2; - private int baseYellowWeight = 60; // Max 10000 - private int basePurpleWeight = 510; // Max 10000 - private int eventChance = 50; // Chance to win a featured event item - private int softPity = 75; - private int hardPity = 90; + private int[] rateUpItems4 = {}; + private int[] rateUpItems5 = {}; + private int[] fallbackItems3 = {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304}; + private int[] fallbackItems4Pool1 = {1014, 1020, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064}; + private int[] fallbackItems4Pool2 = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405}; + private int[] fallbackItems5Pool1 = {1003, 1016, 1042, 1035, 1041}; + private int[] fallbackItems5Pool2 = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502}; + private boolean removeC6FromPool = false; + private boolean autoStripRateUpFromFallback = true; + private int[][] weights4 = {{1,510}, {8,510}, {10,10000}}; + private int[][] weights5 = {{1,75}, {73,150}, {90,10000}}; + private int[][] poolBalanceWeights4 = {{1,255}, {17,255}, {21,10455}}; + private int[][] poolBalanceWeights5 = {{1,30}, {147,150}, {181,10230}}; + private int eventChance4 = 50; // Chance to win a featured event item + private int eventChance5 = 50; // Chance to win a featured event item private BannerType bannerType = BannerType.STANDARD; + + // Kinda wanna deprecate these but they're in people's configs + private int[] rateUpItems1 = {}; + private int[] rateUpItems2 = {}; + private int softPity = -1; + private int hardPity = -1; + private int eventChance = -1; + private int baseYellowWeight = -1; + private int basePurpleWeight = -1; public int getGachaType() { return gachaType; @@ -72,24 +90,42 @@ public class GachaBanner { return basePurpleWeight; } - public int[] getRateUpItems1() { - return rateUpItems1; + public int[] getRateUpItems4() { + return (rateUpItems2.length > 0) ? rateUpItems2 : rateUpItems4; + } + public int[] getRateUpItems5() { + return (rateUpItems1.length > 0) ? rateUpItems1 : rateUpItems5; } - public int[] getRateUpItems2() { - return rateUpItems2; - } - - public int getSoftPity() { - return softPity - 1; + public int[] getFallbackItems3() {return fallbackItems3;} + public int[] getFallbackItems4Pool1() {return fallbackItems4Pool1;} + public int[] getFallbackItems4Pool2() {return fallbackItems4Pool2;} + public int[] getFallbackItems5Pool1() {return fallbackItems5Pool1;} + public int[] getFallbackItems5Pool2() {return fallbackItems5Pool2;} + + public boolean getRemoveC6FromPool() {return removeC6FromPool;} + public boolean getAutoStripRateUpFromFallback() {return autoStripRateUpFromFallback;} + + + public int getWeight(int rarity, int pity) { + return switch(rarity) { + case 4 -> Utils.lerp(pity, weights4); + default -> Utils.lerp(pity, weights5); + }; } - public int getHardPity() { - return hardPity - 1; + public int getPoolBalanceWeight(int rarity, int pity) { + return switch(rarity) { + case 4 -> Utils.lerp(pity, poolBalanceWeights4); + default -> Utils.lerp(pity, poolBalanceWeights5); + }; } - public int getEventChance() { - return eventChance; + public int getEventChance(int rarity) { + return switch(rarity) { + case 4 -> eventChance4; + default -> (eventChance > -1) ? eventChance : eventChance5; + }; } @Deprecated @@ -131,10 +167,10 @@ public class GachaBanner { info.setGachaTitlePath(this.getTitlePath()); } - if (this.getRateUpItems1().length > 0) { + if (this.getRateUpItems5().length > 0) { GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(1); - for (int id : getRateUpItems1()) { + for (int id : getRateUpItems5()) { upInfo.addItemIdList(id); info.addMainNameId(id); } @@ -142,10 +178,10 @@ public class GachaBanner { info.addGachaUpInfoList(upInfo); } - if (this.getRateUpItems2().length > 0) { + if (this.getRateUpItems4().length > 0) { GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(2); - for (int id : getRateUpItems2()) { + for (int id : getRateUpItems4()) { upInfo.addItemIdList(id); if (info.getSubNameIdCount() == 0) { info.addSubNameId(id); diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index 160377913..e3c0153e0 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -29,6 +29,7 @@ import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServerTickEvent; import emu.grasscutter.server.packet.send.PacketDoGachaRsp; +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; @@ -42,12 +43,6 @@ public class GachaManager { private final Int2ObjectMap gachaBanners; private GetGachaInfoRsp cachedProto; WatchService watchService; - - private final int[] yellowAvatars = new int[] {1003, 1016, 1042, 1035, 1041}; - private final int[] yellowWeapons = new int[] {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502}; - private final int[] purpleAvatars = new int[] {1006, 1014, 1015, 1020, 1021, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064}; - private final int[] purpleWeapons = new int[] {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405}; - private final int[] blueWeapons = new int[] {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304}; private static final int starglitterId = 221; private static final int stardustId = 222; @@ -66,24 +61,8 @@ public class GachaManager { public Int2ObjectMap getGachaBanners() { return gachaBanners; } - - public int[] getYellowAvatars() { - return this.yellowAvatars; - } - public int[] getYellowWeapons() { - return this.yellowWeapons; - } - public int[] getPurpleAvatars() { - return this.purpleAvatars; - } - public int[] getPurpleWeapons() { - return this.purpleWeapons; - } - public int[] getBlueWeapons() { - return this.blueWeapons; - } - - public int randomRange(int min, int max) { + + public int randomRange(int min, int max) { // Both are inclusive return ThreadLocalRandom.current().nextInt(max - min + 1) + min; } @@ -98,8 +77,14 @@ public class GachaManager { if(banners.size() > 0) { for (GachaBanner banner : banners) { getGachaBanners().put(banner.getGachaType(), banner); + Grasscutter.getLogger().info(String.format("Testing lerp code for banner gachaType %d :", banner.getGachaType())); // TODO: remove this before merging! + for (int i=1; i<91; i++) { + Grasscutter.getLogger().info(String.format("Pity %d : Weight %d", i, banner.getWeight(5, i))); + } } Grasscutter.getLogger().info("Banners successfully loaded."); + + this.cachedProto = createProto(); } else { Grasscutter.getLogger().error("Unable to load banners. Banners size is 0."); @@ -109,6 +94,139 @@ public class GachaManager { e.printStackTrace(); } } + + 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.getAutoStripRateUpFromFallback()) { + 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); + } + } + + private synchronized int checkPlayerAvatarConstellationLevel(Player player, int itemId) { // Maybe this would be useful in the Player class? + ItemData itemData = GameData.getItemDataMap().get(itemId); + if ((itemData == null) || (itemData.getMaterialType() != MaterialType.MATERIAL_AVATAR)){ + return -2; // Not an Avatar + } + Avatar avatar = player.getAvatars().getAvatarById((itemId % 1000) + 10000000); + if (avatar == null) { + return -1; // Doesn't have + } + // Constellation + int constLevel = avatar.getCoreProudSkillLevel(); + GameItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId + 100); + constLevel += (constItem == null)? 0 : constItem.getCount(); + return constLevel; + } + + private synchronized int[] removeC6FromPool(int[] itemPool, Player player) { + IntList temp = new IntArrayList(); + for (int itemId : itemPool) { + if (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 i : weights) { + if (i < 0) { + throw new IllegalArgumentException("Weights must be non-negative!"); + } + total += i; + } + int roll = ThreadLocalRandom.current().nextInt((total < cutoff)? total : cutoff); + int subTotal = 0; + for (int i : weights) { + subTotal += i; + if (roll < subTotal) { + return i; + } + } + // throw new IllegalStateException(); + return 0; // This should only be reachable if total==0 + } + + private synchronized int doRarePull(int[] featured, int[] fallback1, int[] fallback2, int rarity, GachaBanner banner, PlayerGachaBannerInfo gachaInfo) { + int itemId = 0; + if ( (featured.length > 0) + && (gachaInfo.getFailedFeaturedItemPulls(rarity) >= 1) + || (this.randomRange(1, 100) <= banner.getEventChance(rarity))) { + itemId = getRandom(featured); + gachaInfo.setFailedFeaturedItemPulls(rarity, 0); + } else { + gachaInfo.addFailedFeaturedItemPulls(rarity, 1); + if (fallback1.length < 1) { + itemId = getRandom(fallback2); // Don't ever run an empty fallback2 btw + } else { + 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); + }; + itemId = switch (chosenPool) { + case 1: + gachaInfo.setPityPool(rarity, 1, 0); + yield getRandom(fallback1); + default: + gachaInfo.setPityPool(rarity, 2, 0); + yield getRandom(fallback2); + }; + } + } + 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 gachaType, int times) { // Sanity check @@ -132,84 +250,27 @@ public class GachaManager { return; } - // Roll - PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(banner); - IntList wonItems = new IntArrayList(times); - - for (int i = 0; i < times; i++) { - int random = this.randomRange(1, 10000); - int itemId = 0; - - int bonusYellowChance = gachaInfo.getPity5() >= banner.getSoftPity() ? 100 * (gachaInfo.getPity5() - banner.getSoftPity() - 1): 0; - int yellowChance = banner.getBaseYellowWeight() + (int) Math.floor(100f * (gachaInfo.getPity5() / (banner.getSoftPity() - 1D))) + bonusYellowChance; - int purpleChance = 10000 - (banner.getBasePurpleWeight() + (int) Math.floor(790f * (gachaInfo.getPity4() / 8f))); - - if (random <= yellowChance || gachaInfo.getPity5() >= banner.getHardPity()) { - if (banner.getRateUpItems1().length > 0) { - int eventChance = this.randomRange(1, 100); - - if (eventChance <= banner.getEventChance() || gachaInfo.getFailedFeaturedItemPulls() >= 1) { - itemId = getRandom(banner.getRateUpItems1()); - gachaInfo.setFailedFeaturedItemPulls(0); - } else { - // Lost the 50/50... rip - gachaInfo.addFailedFeaturedItemPulls(1); - } - } - - if (itemId == 0) { - int typeChance = this.randomRange(banner.getBannerType() == BannerType.WEAPON ? 2 : 1, banner.getBannerType() == BannerType.EVENT ? 1 : 2); - if (typeChance == 1) { - itemId = getRandom(this.yellowAvatars); - } else { - itemId = getRandom(this.yellowWeapons); - } - } - - // Pity - gachaInfo.addPity4(1); - gachaInfo.setPity5(0); - } else if (random >= purpleChance || gachaInfo.getPity4() >= 9) { - if (banner.getRateUpItems2().length > 0) { - int eventChance = this.randomRange(1, 100); - - if (eventChance >= 50) { - itemId = getRandom(banner.getRateUpItems2()); - } - } - - if (itemId == 0) { - int typeChance = this.randomRange(banner.getBannerType() == BannerType.WEAPON ? 2 : 1, banner.getBannerType() == BannerType.EVENT ? 1 : 2); - if (typeChance == 1) { - itemId = getRandom(this.purpleAvatars); - } else { - itemId = getRandom(this.purpleWeapons); - } - } - - // Pity - gachaInfo.addPity5(1); - gachaInfo.setPity4(0); - } else { - itemId = getRandom(this.blueWeapons); - - // Pity - gachaInfo.addPity4(1); - gachaInfo.addPity5(1); - } - - // Add winning item - wonItems.add(itemId); - } - // Add to character + PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(banner); + BannerPools pools = new BannerPools(banner); List list = new ArrayList<>(); int stardust = 0, starglitter = 0; + + if (banner.getRemoveC6FromPool()) { // 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 itemId : wonItems) { + for (int i = 0; i < times; i++) { + // Roll + int itemId = doPull(banner, gachaInfo, pools); ItemData itemData = GameData.getItemDataMap().get(itemId); if (itemData == null) { - continue; + continue; // Maybe we should bail out if an item fails instead of rolling the rest? } // Write gacha record @@ -222,44 +283,33 @@ public class GachaManager { boolean isTransferItem = false; // Const check - if (itemData.getMaterialType() == MaterialType.MATERIAL_AVATAR) { - int avatarId = (itemData.getId() % 1000) + 10000000; - Avatar avatar = player.getAvatars().getAvatarById(avatarId); - if (avatar != null) { - int constLevel = avatar.getCoreProudSkillLevel(); - int constItemId = itemData.getId() + 100; - GameItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId); - if (constItem != null) { - constLevel += constItem.getCount(); + int constellation = checkPlayerAvatarConstellationLevel(player, itemId); + switch (constellation) { + case -2: // Is weapon + switch (itemData.getRankLevel()) { + case 5 -> addStarglitter = 10; + case 4 -> addStarglitter = 2; + default -> addStardust = 15; } - - if (constLevel < 6) { - // Not max const - addStarglitter = 2; - // Add 1 const + 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.getRemoveC6FromPool() && 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; + GameItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId); gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1)).setIsTransferItemNew(constItem == null)); player.getInventory().addItem(constItemId, 1); - } else { - // Is max const - addStarglitter = 5; } - - if (itemData.getRankLevel() == 5) { - addStarglitter *= 5; - } - isTransferItem = true; - } else { - // New - gachaItem.setIsGachaItemNew(true); - } - } else { - // Is weapon - switch (itemData.getRankLevel()) { - case 5 -> addStarglitter = 10; - case 4 -> addStarglitter = 2; - case 3 -> addStardust = 15; - } + break; } // Create item @@ -272,7 +322,8 @@ public class GachaManager { if (addStardust > 0) { gachaItem.addTokenItemList(ItemParam.newBuilder().setItemId(stardustId).setCount(addStardust)); - } if (addStarglitter > 0) { + } + if (addStarglitter > 0) { ItemParam starglitterParam = ItemParam.newBuilder().setItemId(starglitterId).setCount(addStarglitter).build(); if (isTransferItem) { gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(starglitterParam)); @@ -286,7 +337,8 @@ public class GachaManager { // Add stardust/starglitter if (stardust > 0) { player.getInventory().addItem(stardustId, stardust); - } if (starglitter > 0) { + } + if (starglitter > 0) { player.getInventory().addItem(starglitterId, starglitter); } diff --git a/src/main/java/emu/grasscutter/game/gacha/PlayerGachaBannerInfo.java b/src/main/java/emu/grasscutter/game/gacha/PlayerGachaBannerInfo.java index b0c85d355..f07d2eff0 100644 --- a/src/main/java/emu/grasscutter/game/gacha/PlayerGachaBannerInfo.java +++ b/src/main/java/emu/grasscutter/game/gacha/PlayerGachaBannerInfo.java @@ -7,6 +7,11 @@ public class PlayerGachaBannerInfo { private int pity5 = 0; 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; public int getPity5() { return pity5; @@ -32,15 +37,82 @@ public class PlayerGachaBannerInfo { this.pity4 += amount; } - public int getFailedFeaturedItemPulls() { - return failedFeaturedItemPulls; + public int getFailedFeaturedItemPulls(int rarity) { + return switch (rarity) { + case 4 -> failedFeatured4ItemPulls; + default -> failedFeaturedItemPulls; // 5 + }; } - public void setFailedFeaturedItemPulls(int failedEventCharacterPulls) { - this.failedFeaturedItemPulls = failedEventCharacterPulls; + public void setFailedFeaturedItemPulls(int rarity, int amount) { + switch (rarity) { + case 4 -> failedFeatured4ItemPulls = amount; + default -> failedFeaturedItemPulls = amount; // 5 + }; } - public void addFailedFeaturedItemPulls(int amount) { - failedFeaturedItemPulls += amount; + public void addFailedFeaturedItemPulls(int rarity, int amount) { + switch (rarity) { + case 4 -> failedFeatured4ItemPulls += amount; + default -> 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: + switch (pool) { + case 1 -> pity4Pool1 = amount; + default -> pity4Pool2 = amount; + }; + break; + case 5: + default: + switch (pool) { + case 1 -> pity5Pool1 = amount; + default -> pity5Pool2 = amount; + }; + break; + }; + } + + public void addPityPool(int rarity, int pool, int amount) { + switch (rarity) { + case 4: + switch (pool) { + case 1 -> pity4Pool1 += amount; + default -> pity4Pool2 += amount; + }; + break; + case 5: + default: + switch (pool) { + case 1 -> pity5Pool1 += amount; + default -> pity5Pool2 += amount; + }; + break; + }; + } + + public void incPityAll() { + pity4++; + pity5++; + pity4Pool1++; + pity4Pool2++; + pity5Pool1++; + pity5Pool2++; } } diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index 4af62bfb4..58fc83cd0 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -15,6 +15,8 @@ import emu.grasscutter.Grasscutter; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; import org.slf4j.Logger; @@ -314,4 +316,68 @@ public final class Utils { return String.format("%s-%s", locale.getLanguage(), locale.getCountry()); } + /** + * Performs a linear interpolation using a table of fixed points to create an effective piecewise f(x) = y function. + * @param x + * @param xyArray Array of points in [[x0,y0], ... [xN, yN]] format + * @return f(x) = y + */ + public static int lerp(int x, int[][] xyArray) { + try { + if (x <= xyArray[0][0]){ // Clamp to first point + return xyArray[0][1]; + } else if (x >= xyArray[xyArray.length-1][0]) { // Clamp to last point + return xyArray[xyArray.length-1][1]; + } + // At this point we're guaranteed to have two lerp points, and pity be somewhere between them. + for (int i=0; i < xyArray.length-1; i++) { + if (x == xyArray[i+1][0]) { + return xyArray[i+1][1]; + } + if (x < xyArray[i+1][0]) { + // We are between [i] and [i+1], interpolation time! + // Using floats would be slightly cleaner but we can just as easily use ints if we're careful with order of operations. + int position = x - xyArray[i][0]; + int fullDist = xyArray[i+1][0] - xyArray[i][0]; + int prevValue = xyArray[i][1]; + int fullDelta = xyArray[i+1][1] - prevValue; + return prevValue + ( (position * fullDelta) / fullDist ); + } + } + } catch (IndexOutOfBoundsException e) { + Grasscutter.getLogger().error("Malformed lerp point array. Must be of form [[x0, y0], ..., [xN, yN]]."); + } + return 0; + } + + /** + * Checks if an int is in an int[] + * @param key int to look for + * @param array int[] to look in + * @return key in array + */ + public static boolean intInArray(int key, int[] array) { + for (int i : array) { + if (i == key) { + return true; + } + } + return false; + } + + /** + * Return a copy of minuend without any elements found in subtrahend. + * @param minuend The array we want elements from + * @param subtrahend The array whose elements we don't want + * @return The array with only the elements we want, in the order that minuend had them + */ + public static int[] setSubtract(int[] minuend, int[] subtrahend) { + IntList temp = new IntArrayList(); + for (int i : minuend) { + if (!intInArray(i, subtrahend)) { + temp.add(i); + } + } + return temp.toIntArray(); + } } From 83c46cb9c8254d2fe84b11f320fe0741e10548a7 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Wed, 11 May 2022 19:24:59 +0930 Subject: [PATCH 127/312] Custom costs for different gacha pulls --- .../grasscutter/game/gacha/GachaBanner.java | 29 ++++++++++--------- .../grasscutter/game/gacha/GachaManager.java | 17 ++++++----- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java index 52630768c..3c97e671c 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -6,13 +6,18 @@ import emu.grasscutter.utils.Utils; import static emu.grasscutter.Configuration.*; +import emu.grasscutter.data.common.ItemParamData; + public class GachaBanner { private int gachaType; private int scheduleId; private String prefabPath; private String previewPrefabPath; private String titlePath; - private int costItem; + private int costItemId = 0; + private int costItemAmount = 1; + private int costItemId10 = 0; + private int costItemAmount10 = 10; private int beginTime; private int endTime; private int sortId; @@ -36,11 +41,8 @@ public class GachaBanner { // Kinda wanna deprecate these but they're in people's configs private int[] rateUpItems1 = {}; private int[] rateUpItems2 = {}; - private int softPity = -1; - private int hardPity = -1; private int eventChance = -1; - private int baseYellowWeight = -1; - private int basePurpleWeight = -1; + private int costItem = 0; public int getGachaType() { return gachaType; @@ -66,8 +68,15 @@ public class GachaBanner { return titlePath; } + public ItemParamData getCost(int numRolls) { + return switch (numRolls) { + case 10 -> new ItemParamData((costItemId10 > 0) ? costItemId10 : getCostItem(), costItemAmount10); + default -> new ItemParamData(getCostItem(), costItemAmount * numRolls); + }; + } + public int getCostItem() { - return costItem; + return (costItem > 0) ? costItem : costItemId; } public int getBeginTime() { @@ -82,14 +91,6 @@ public class GachaBanner { return sortId; } - public int getBaseYellowWeight() { - return baseYellowWeight; - } - - public int getBasePurpleWeight() { - return basePurpleWeight; - } - public int[] getRateUpItems4() { return (rateUpItems2.length > 0) ? rateUpItems2 : rateUpItems4; } diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index e3c0153e0..11945c3d3 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -19,6 +19,7 @@ import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.avatar.Avatar; 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.inventory.MaterialType; import emu.grasscutter.game.player.Player; @@ -233,7 +234,8 @@ public class GachaManager { if (times != 10 && times != 1) { return; } - if (player.getInventory().getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times > player.getInventory().getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) { + Inventory inventory = player.getInventory(); + if (inventory.getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times > inventory.getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) { player.sendPacket(new PacketDoGachaRsp()); return; } @@ -246,7 +248,8 @@ public class GachaManager { } // Spend currency - if (banner.getCostItem() > 0 && !player.getInventory().payItem(banner.getCostItem(), times)) { + ItemParamData cost = banner.getCost(times); + if (cost.getCount() > 0 && !inventory.payItem(cost)) { return; } @@ -304,9 +307,9 @@ public class GachaManager { } addStarglitter = (itemData.getRankLevel()==5)? 10 : 2; int constItemId = itemId + 100; - GameItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId); + GameItem constItem = inventory.getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId); gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1)).setIsTransferItemNew(constItem == null)); - player.getInventory().addItem(constItemId, 1); + inventory.addItem(constItemId, 1); } isTransferItem = true; break; @@ -315,7 +318,7 @@ public class GachaManager { // Create item GameItem item = new GameItem(itemData); gachaItem.setGachaItem(item.toItemParam()); - player.getInventory().addItem(item); + inventory.addItem(item); stardust += addStardust; starglitter += addStarglitter; @@ -336,10 +339,10 @@ public class GachaManager { // Add stardust/starglitter if (stardust > 0) { - player.getInventory().addItem(stardustId, stardust); + inventory.addItem(stardustId, stardust); } if (starglitter > 0) { - player.getInventory().addItem(starglitterId, starglitter); + inventory.addItem(starglitterId, starglitter); } // Packets From 736ca85300d79c188d5f779614ee2179f2b241e2 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Thu, 12 May 2022 00:14:26 +0930 Subject: [PATCH 128/312] Remove debug log from gacha --- src/main/java/emu/grasscutter/game/gacha/GachaManager.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index 11945c3d3..a0337be7f 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -17,7 +17,6 @@ import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.def.ItemData; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.avatar.Avatar; -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; @@ -78,10 +77,6 @@ public class GachaManager { if(banners.size() > 0) { for (GachaBanner banner : banners) { getGachaBanners().put(banner.getGachaType(), banner); - Grasscutter.getLogger().info(String.format("Testing lerp code for banner gachaType %d :", banner.getGachaType())); // TODO: remove this before merging! - for (int i=1; i<91; i++) { - Grasscutter.getLogger().info(String.format("Pity %d : Weight %d", i, banner.getWeight(5, i))); - } } Grasscutter.getLogger().info("Banners successfully loaded."); From 420801b49e2d0f882064b4830fa9ba35cefedff1 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Thu, 12 May 2022 02:23:51 +0930 Subject: [PATCH 129/312] Updated cost logic and default weights --- data/Banners.json | 9 ++++-- .../grasscutter/game/gacha/GachaBanner.java | 12 ++++---- .../grasscutter/game/gacha/GachaManager.java | 30 ++++++++++++------- .../server/packet/send/PacketDoGachaRsp.java | 11 ++++--- 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/data/Banners.json b/data/Banners.json index 1aaf39cb7..17e720e65 100644 --- a/data/Banners.json +++ b/data/Banners.json @@ -6,7 +6,9 @@ "prefabPath": "GachaShowPanel_A022", "previewPrefabPath": "UI_Tab_GachaShowPanel_A022", "titlePath": "UI_GACHA_SHOW_PANEL_A022_TITLE", - "costItem": 224, + "costItemId": 224, + "costItemAmount": 1, + "costItemAmount10": 10, "beginTime": 0, "endTime": 1924992000, "sortId": 1000, @@ -21,7 +23,7 @@ "prefabPath": "GachaShowPanel_A079", "previewPrefabPath": "UI_Tab_GachaShowPanel_A079", "titlePath": "UI_GACHA_SHOW_PANEL_A048_TITLE", - "costItem": 223, + "costItemId": 223, "beginTime": 0, "endTime": 1924992000, "sortId": 9998, @@ -37,7 +39,7 @@ "prefabPath": "GachaShowPanel_A080", "previewPrefabPath": "UI_Tab_GachaShowPanel_A080", "titlePath": "UI_GACHA_SHOW_PANEL_A021_TITLE", - "costItem": 223, + "costItemId": 223, "beginTime": 0, "endTime": 1924992000, "sortId": 9997, @@ -47,6 +49,7 @@ "rateUpItems4": [11401, 12402, 13407, 14401, 15401], "rateUpItems5": [11509, 12504], "fallbackItems5Pool1": [], + "weights4": [[1,600], [7,600], [8, 6600], [10,12600]], "weights5": [[1,100], [62,100], [73, 7800], [80,10000]] } ] diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java index 3c97e671c..1586198c2 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -6,6 +6,7 @@ import emu.grasscutter.utils.Utils; import static emu.grasscutter.Configuration.*; +import emu.grasscutter.Grasscutter; import emu.grasscutter.data.common.ItemParamData; public class GachaBanner { @@ -145,25 +146,26 @@ public class GachaBanner { + "/gacha/details?s=" + sessionKey + "&gachaType=" + gachaType; // Grasscutter.getLogger().info("record = " + record); + ItemParamData costItem1 = this.getCost(1); + ItemParamData costItem10 = this.getCost(10); GachaInfo.Builder info = GachaInfo.newBuilder() .setGachaType(this.getGachaType()) .setScheduleId(this.getScheduleId()) .setBeginTime(this.getBeginTime()) .setEndTime(this.getEndTime()) - .setCostItemId(this.getCostItem()) - .setCostItemNum(1) + .setCostItemId(costItem1.getId()) + .setCostItemNum(costItem1.getCount()) + .setTenCostItemId(costItem10.getId()) + .setTenCostItemNum(costItem10.getCount()) .setGachaPrefabPath(this.getPrefabPath()) .setGachaPreviewPrefabPath(this.getPreviewPrefabPath()) .setGachaProbUrl(details) .setGachaProbUrlOversea(details) .setGachaRecordUrl(record) .setGachaRecordUrlOversea(record) - .setTenCostItemId(this.getCostItem()) - .setTenCostItemNum(10) .setLeftGachaTimes(Integer.MAX_VALUE) .setGachaTimesLimit(Integer.MAX_VALUE) .setGachaSortId(this.getSortId()); - if (this.getTitlePath() != null) { info.setGachaTitlePath(this.getTitlePath()); } diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index a0337be7f..4cbfde094 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -4,6 +4,7 @@ import java.io.File; import java.io.FileReader; import java.nio.file.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.concurrent.ThreadLocalRandom; @@ -46,6 +47,8 @@ public class GachaManager { private static final int starglitterId = 221; private static final int stardustId = 222; + private int[] fallbackItems4Pool2Default = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405}; + private int[] fallbackItems5Pool2Default = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502}; public GachaManager(GameServer server) { this.server = server; @@ -156,16 +159,16 @@ public class GachaManager { // 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 i : weights) { - if (i < 0) { + for (int weight : weights) { + if (weight < 0) { throw new IllegalArgumentException("Weights must be non-negative!"); } - total += i; + total += weight; } int roll = ThreadLocalRandom.current().nextInt((total < cutoff)? total : cutoff); int subTotal = 0; - for (int i : weights) { - subTotal += i; + for (int i=0; i 0) - && (gachaInfo.getFailedFeaturedItemPulls(rarity) >= 1) - || (this.randomRange(1, 100) <= banner.getEventChance(rarity))) { + boolean pullFeatured = (gachaInfo.getFailedFeaturedItemPulls(rarity) >= 1) // Lost previous coinflip + || (this.randomRange(1, 100) <= banner.getEventChance(rarity)); // Won this coinflip + if (pullFeatured && (featured.length > 0)) { itemId = getRandom(featured); gachaInfo.setFailedFeaturedItemPulls(rarity, 0); } else { gachaInfo.addFailedFeaturedItemPulls(rarity, 1); if (fallback1.length < 1) { - itemId = getRandom(fallback2); // Don't ever run an empty fallback2 btw - } else { + if (fallback2.length < 1) { + itemId = getRandom((rarity==5)? fallbackItems5Pool2Default : fallbackItems4Pool2Default); + } else { + itemId = getRandom(fallback2); + } + } else if (fallback2.length < 1) { + itemId = 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 @@ -245,6 +254,7 @@ public class GachaManager { // Spend currency ItemParamData cost = banner.getCost(times); if (cost.getCount() > 0 && !inventory.payItem(cost)) { + player.sendPacket(new PacketDoGachaRsp()); return; } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDoGachaRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDoGachaRsp.java index 9144c0d8e..6d8b9ddd9 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketDoGachaRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDoGachaRsp.java @@ -2,6 +2,7 @@ package emu.grasscutter.server.packet.send; import java.util.List; +import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.game.gacha.GachaBanner; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; @@ -14,16 +15,18 @@ public class PacketDoGachaRsp extends BasePacket { public PacketDoGachaRsp(GachaBanner banner, List list) { super(PacketOpcodes.DoGachaRsp); + ItemParamData costItem = banner.getCost(1); + ItemParamData costItem10 = banner.getCost(10); DoGachaRsp p = DoGachaRsp.newBuilder() .setGachaType(banner.getGachaType()) .setGachaScheduleId(banner.getScheduleId()) .setGachaTimes(list.size()) .setNewGachaRandom(12345) .setLeftGachaTimes(Integer.MAX_VALUE) - .setCostItemId(banner.getCostItem()) - .setCostItemNum(1) - .setTenCostItemId(banner.getCostItem()) - .setTenCostItemNum(10) + .setCostItemId(costItem.getId()) + .setCostItemNum(costItem.getCount()) + .setTenCostItemId(costItem10.getId()) + .setTenCostItemNum(costItem10.getCount()) .addAllGachaItemList(list) .build(); From 395da1ebcb229ac999282a536025808b5dee57a0 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Thu, 12 May 2022 15:00:40 +0930 Subject: [PATCH 130/312] Fix gachadetails --- .../dispatch/http/GachaDetailsHandler.java | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java b/src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java index 5e1877b9b..e5359a9da 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java +++ b/src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java @@ -62,28 +62,24 @@ public final class GachaDetailsHandler implements HttpContextHandler { // Add 5-star items. Set fiveStarItems = new LinkedHashSet<>(); - Arrays.stream(banner.getRateUpItems1()).forEach(i -> fiveStarItems.add(Integer.toString(i))); - if (banner.getBannerType() == BannerType.STANDARD || banner.getBannerType() == BannerType.EVENT) { - Arrays.stream(manager.getYellowAvatars()).forEach(i -> fiveStarItems.add(Integer.toString(i))); - } - if (banner.getBannerType() == BannerType.STANDARD || banner.getBannerType() == BannerType.WEAPON) { - Arrays.stream(manager.getYellowWeapons()).forEach(i -> fiveStarItems.add(Integer.toString(i))); - } + Arrays.stream(banner.getRateUpItems5()).forEach(i -> fiveStarItems.add(Integer.toString(i))); + Arrays.stream(banner.getFallbackItems5Pool1()).forEach(i -> fiveStarItems.add(Integer.toString(i))); + Arrays.stream(banner.getFallbackItems5Pool2()).forEach(i -> fiveStarItems.add(Integer.toString(i))); response = response.replace("{{FIVE_STARS}}", "[" + String.join(",", fiveStarItems) + "]"); // Add 4-star items. Set fourStarItems = new LinkedHashSet<>(); - Arrays.stream(banner.getRateUpItems2()).forEach(i -> fourStarItems.add(Integer.toString(i))); - Arrays.stream(manager.getPurpleAvatars()).forEach(i -> fourStarItems.add(Integer.toString(i))); - Arrays.stream(manager.getPurpleWeapons()).forEach(i -> fourStarItems.add(Integer.toString(i))); + Arrays.stream(banner.getRateUpItems4()).forEach(i -> fourStarItems.add(Integer.toString(i))); + Arrays.stream(banner.getFallbackItems4Pool1()).forEach(i -> fourStarItems.add(Integer.toString(i))); + Arrays.stream(banner.getFallbackItems4Pool2()).forEach(i -> fourStarItems.add(Integer.toString(i))); response = response.replace("{{FOUR_STARS}}", "[" + String.join(",", fourStarItems) + "]"); // Add 3-star items. Set threeStarItems = new LinkedHashSet<>(); - Arrays.stream(manager.getBlueWeapons()).forEach(i -> threeStarItems.add(Integer.toString(i))); + Arrays.stream(banner.getFallbackItems3()).forEach(i -> threeStarItems.add(Integer.toString(i))); response = response.replace("{{THREE_STARS}}", "[" + String.join(",", threeStarItems) + "]"); // Done. From d4eb686fe2f7bd1afd7ee8c74068b7e6fb565fab Mon Sep 17 00:00:00 2001 From: kyoko12 Date: Fri, 13 May 2022 14:10:02 +0200 Subject: [PATCH 131/312] Don't silently delete config.json if there is an error. --- .../java/emu/grasscutter/Grasscutter.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 2af2415ab..af8a759a5 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -153,16 +153,22 @@ public final class Grasscutter { * Attempts to load the configuration from a file. */ public static void loadConfig() { + // Check if config.json exists. If not, we generate a new config. + if (!configFile.exists()) { + getLogger().info("config.json could not be found. Generating a default configuration ..."); + config = new ConfigContainer(); + Grasscutter.saveConfig(config); + return; + } + + // If the file already exists, we attempt to load it. try (FileReader file = new FileReader(configFile)) { config = gson.fromJson(file, ConfigContainer.class); - } catch (Exception exception) { - Grasscutter.saveConfig(null); - config = new ConfigContainer(); - } catch (Error error) { - // Occurred probably from an outdated config file. - Grasscutter.saveConfig(null); - config = new ConfigContainer(); - } + } + catch (Exception exception) { + getLogger().error("There was an error while trying to load the configuration from config.json. Please make sure that there are no syntax errors. If you want to start with a default configuration, delete your existing config.json."); + System.exit(1); + } } public static void loadLanguage() { From 891c70e5ea8689c8c0e799bc3a50a7b31cb99940 Mon Sep 17 00:00:00 2001 From: Kimi <34180607+Kimi898246@users.noreply.github.com> Date: Fri, 13 May 2022 20:10:40 +0800 Subject: [PATCH 132/312] Traditional Chinese | Translation Patches --- src/main/resources/languages/zh-TW.json | 146 ++++++++++++++++-------- 1 file changed, 98 insertions(+), 48 deletions(-) diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index f6ae52c9d..2b6fe34ff 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -16,6 +16,9 @@ "no_keystore_error": "[Dispatch] 未找到 SSL 憑證!已後降到 HTTP 伺服器。", "default_password": "[Dispatch] 默認的 keystore 密碼加載成功。請考慮將 config.json 的憑證密碼設定成 123456。" }, + "authentication": { + "default_unable_to_verify": "[驗證系統] 稱為 verifyUser 方法的東西在默認身份驗證程序中不可用。" + }, "no_commands_error": "此指令不適用於Dispatch-only模式。", "unhandled_request_error": "[Dispatch] 潛在的未處理請求 %s 請求:%s", "account": { @@ -46,7 +49,7 @@ "run_mode_help": "伺服器運行模式必須為 HYBRID 或者 DISPATCH_ONLY 或者 GAME_ONLY。Grasscutter 啟動失敗...", "create_resources": "正在建立 resources 資料夾...", "resources_error": "請將 BinOutput 和 ExcelBinOutput 複製到 resources 資料夾。", - "version": "Grasscutter版本: %s-%s" + "version": "Grasscutter版本: %s-%s" } }, "commands": { @@ -57,6 +60,7 @@ "console_execute_error": "此指令只能在伺服器的命令提示字元執行。", "player_execute_error": "請在遊戲裡使用這條指令。", "command_exist_error": "找不到指令。", + "no_description_specified": "没有指定說明。", "invalid": { "amount": "無效的數量。", "artifactId": "無效的聖遺物ID。", @@ -96,17 +100,20 @@ "create": "已建立帳號,UID 為 %s 。", "delete": "帳號已刪除。", "no_account": "帳號不存在。", - "command_usage": "用法:account [uid]" + "command_usage": "用法:account [uid]", + "description": "建立或刪除帳號。" }, "broadcast": { "command_usage": "用法:broadcast ", - "message_sent": "公告已發送。" + "message_sent": "公告已發送。", + "description": "向所有玩家發送公告。" }, "changescene": { "usage": "用法:changescene ", "already_in_scene": "你已經在這個場景中了。", "success": "已切換至場景 %s.", - "exists_error": "此場景不存在。" + "exists_error": "此場景不存在。", + "description": "切換指定場景。" }, "clear": { "command_usage": "用法: clear ", @@ -116,35 +123,41 @@ "furniture": "已將 %s 的塵歌壺家具清空。", "displays": "已清除 %s 的顯示。", "virtuals": "已將 %s 的所有貨幣和經驗值清空。", - "everything": "已將 %s 的所有物品清空。" + "everything": "已將 %s 的所有物品清空。", + "description": "從你的背包中刪除所有未裝備且未上鎖的物品,包括稀有物品。" }, "coop": { "usage": "用法:coop ", - "success": "召喚了 %s 到 %s 的世界。" + "success": "召喚了 %s 到 %s 的世界。", + "description": "強制傳送指定用戶到他人的世界。" }, "enter_dungeon": { "usage": "用法:enterdungeon ", - "changed": "已進入副本 %s", - "not_found_error": "此副本不存在。", - "in_dungeon_error": "你已經在祕境中了。" + "changed": "已進入祕境 %s", + "not_found_error": "此祕境不存在。", + "in_dungeon_error": "你已經在祕境中了。", + "description": "進入指定祕境。" }, "giveAll": { "usage": "用法:giveall [player] [amount]", "started": "正在賦予全部物品...", "success": "已賦予全部物品。", - "invalid_amount_or_playerId": "無效的數量/玩家ID。" + "invalid_amount_or_playerId": "無效的數量/玩家ID。", + "description": "賦予所有物品。" }, "giveArtifact": { "usage": "用法:giveart|gart [player] [[,]]... [level]", "id_error": "無效的聖遺物ID。", - "success": "已把 %s 給予 %s。" + "success": "已把 %s 給予 %s。", + "description": "給予指定聖遺物。" }, "giveChar": { "usage": "用法:givechar [amount]", "given": "已將 %s 等級 %s 給予 %s。", "invalid_avatar_id": "無效的角色ID。", "invalid_avatar_level": "無效的角色等級。.", - "invalid_avatar_or_player_id": "無效的角色ID/玩家ID。" + "invalid_avatar_or_player_id": "無效的角色ID/玩家ID。", + "description": "給予指定角色。" }, "give": { "usage": "用法:give [amount] [level]", @@ -152,29 +165,42 @@ "refinement_must_between_1_and_5": "精煉度必需在 1 到 5 之間。", "given": "已經將 %s 個 %s 給予 %s。", "given_with_level_and_refinement": "已將 %s [等級%s, 精煉%s] %s個給予 %s", - "given_level": "已將 %s 等級 %s %s 個給予 %s" + "given_level": "已將 %s 等級 %s %s 個給予 %s", + "description": "給予指定物品。" }, "godmode": { - "success": "上帝模式設定為 %s 。 [用戶:%s]" + "success": "上帝模式設定為 %s 。 [用戶:%s]", + "description": "防止你受到傷害。" }, "heal": { - "success": "所有角色已被治療。" + "success": "所有角色已被治療。", + "description": "治療當前隊伍的角色。" }, "kick": { "player_kick_player": "玩家 [%s:%s] 已把 [%s:%s] 踢出", - "server_kick_player": "正在踢出玩家 [%s:%s]" + "server_kick_player": "正在踢出玩家 [%s:%s]", + "description": "從伺服器內踢出指定玩家。" }, "kill": { "usage": "用法:killall [playerUid] [sceneId]", "scene_not_found_in_player_world": "未在玩家世界中找到此場景", - "kill_monsters_in_scene": "已殺死 %s 個怪物。 [場景ID: %s]" + "kill_monsters_in_scene": "已殺死 %s 個怪物。 [場景ID: %s]", + "description": "殺死所有怪物。" }, "killCharacter": { "usage": "用法:/killcharacter [playerId]", - "success": "已殺死 %s 目前的場上角色。" + "success": "已殺死 %s 目前的場上角色。", + "description": "殺死玩家目前使用的場上角色。" + }, + "language": { + "current_language": "當前語言是: %s", + "language_changed": "語言切換至: %s", + "language_not_found": "目前客戶端沒有這種語言: %s", + "description": "顯示或切換當前語言。" }, "list": { - "success": "目前總線上人數:%s" + "success": "目前總線上人數:%s" , + "description": "查看所有在線玩家" }, "permission": { "usage": "用法:permission ", @@ -182,21 +208,26 @@ "has_error": "此玩家已擁有權限!", "remove": "權限已移除。", "not_have_error": "此玩家未擁有權限!", - "account_error": "The account cannot be found." + "account_error": "帳號不存在。", + "description": "指派或移除指定玩家的權限。" }, "position": { - "success": "坐標:%s, %s, %s\n場景ID:%s" + "success": "座標:%s, %s, %s\n場景ID:%s", + "description": "獲取目前所在位置的座標。" }, "reload": { "reload_start": "正在重新加載設定檔。", - "reload_done": "重新加載已完成。" + "reload_done": "重新加載已完成。", + "description": "重新加載設定檔和數據。" }, "resetConst": { "reset_all": "重設所有角色的命座。", - "success": "已重設 %s 的命座,重新登入後將會生效。" + "success": "已重設 %s 的命座,重新登入後將會生效。", + "description": "重置當前角色的命之座,重新登入後將會生效。" }, "resetShopLimit": { - "usage": "用法:/resetshop " + "usage": "用法:/resetshop ", + "description": "重置所選玩家的商店刷新時間。" }, "sendMail": { "usage": "用法:give [player] [amount]", @@ -218,17 +249,20 @@ "message": "<正文>", "sender": "<寄件者>", "arguments": " [數量] [等級]", - "error": "錯誤:無效的編寫階段 %s。需要 stacktrace 請查看伺服器命令提示字元。" + "error": "錯誤:無效的編寫階段 %s。需要 stacktrace 請查看伺服器命令提示字元。", + "description": "向指定用戶發送郵件。此指令的用法可根據附加的參數而改變。" }, "sendMessage": { "usage": "用法:sendmessage ", - "success": "訊息已發送。" + "success": "訊息已發送。", + "description": "向指定玩家發送訊息。" }, "setFetterLevel": { "usage": "用法:setfetterlevel ", "range_error": "好感度必須在 0 到 10 之間。", "success": "好感等級已設定為 %s", - "level_error": "無效的好感度。" + "level_error": "無效的好感度。", + "description": "設定當前角色的好感度等級。" }, "setStats": { "usage_console": "用法:setstats|stats @ ", @@ -239,77 +273,93 @@ "player_error": "玩家不存在或已離線。", "set_self": "%s 已經設為 %s。", "set_for_uid": "%s 的使用者 %s 更改為 %s。", - "set_max_hp": "最大生命值更改為 %s。" + "set_max_hp": "最大生命值更改為 %s。", + "description": "設定當前角色的數據類型。" }, "setWorldLevel": { "usage": "用法:setworldlevel ", "value_error": "世界等級必須設定在0-8之間。", "success": "已將世界等級設為%s。", - "invalid_world_level": "無效的世界等級。" + "invalid_world_level": "無效的世界等級。", + "description": "設定世界等級,執行指令後需重新登入後才會生效。" }, "spawn": { "usage": "用法:spawn [amount] [level(僅限怪物)]", - "success": "已生成 %s 個 %s。" + "success": "已生成 %s 個 %s。", + "description": "在你附近生成一個實體動物。" }, "stop": { - "success": "正在關閉伺服器..." + "success": "正在關閉伺服器...", + "description": "以正常的方式關閉伺服器。" }, "talent": { "usage_1": "設定天賦等級:/talent set ", "usage_2": "另一種設定天賦等級的指令使用方法:/talent ", "usage_3": "獲取天賦ID指令用法:/talent getid", - "lower_16": "無效的技能等級,技能等級應低於 16。", + "lower_16": "無效的天賦等級,技能等級應低於 16。", "set_id": "將天賦等級設為%s。", "set_atk": "將普通攻擊等級設為 %s。", - "set_e": "設定天賦E等級至 %s。", - "set_q": "設定天賦Q等級至 %s。", + "set_e": "設定元素戰技的天賦等級至 %s。", + "set_q": "設定元素爆發的天賦等級至 %s。", "invalid_skill_id": "無效的技能ID。", "set_this": "將天賦等級設為 %s。", "invalid_level": "無效的天賦等級。", "normal_attack_id": "普通攻擊的 ID 為 %s。", - "e_skill_id": "E技能ID %s。", - "q_skill_id": "Q技能ID %s。" + "e_skill_id": "元素戰技技能ID %s。", + "q_skill_id": "元素爆發技能ID %s。", + "description": "設定當前角色的天賦等級" }, "teleportAll": { "success": "召喚了所有玩家到你的位置上。", - "error": "此指令僅可在多人遊戲下可用。" + "error": "此指令僅可在多人遊戲下可用。", + "description": "將你世界裡的所有玩家傳送到你目前的所在位置。" }, "teleport": { "usage_server": "用法:/tp @ [scene id]", "usage": "用法:/tp [@] [scene id]", "specify_player_id": "你必須指定一個玩家ID。", - "invalid_position": "無效的位置。", - "success": "傳送 %s 到坐標 %s,%s,%s ,場景為 %s" + "invalid_position": "無效的座標。", + "success": "傳送 %s 到座標 %s,%s,%s ,場景為 %s 。", + "description": "將玩家的位置傳送到你所指定的座標。" + }, + "tower": { + "unlock_done": "解鎖所有級別的深境螺旋已全部解鎖。" }, "weather": { "usage": "用法:weather [climateId]", "success": "已將當前天氣設定為 %s ,氣候則為 %s 。", - "invalid_id": "無效的ID。" + "invalid_id": "無效的ID。", + "description": "更改目前的天氣。" }, "drop": { "command_usage": "用法:drop [amount]", - "success": "已將 %s x %s 丟在附近。" + "success": "已將 %s x %s 丟在附近。", + "description": "在你附近丟下一個物品。" }, "help": { "usage": "用法:", "aliases": "別名:", + "description": "發送幫助信息或顯示特定命令的信息", "available_commands": "可用指令:" }, + "restart": { + "description": "重新啟動伺服器。" + }, "unlocktower": { "success": "解鎖完成。", - "description": "解鎖所有級別的深境螺旋" + "description": "解鎖所有級別的深境螺旋。" }, "resetshop": { - "description": "重置商店時間" + "description": "重置商店刷新時間。" } }, "gacha": { "details": { - "title": "Banner Details", - "available_five_stars": "Available 5-star Items", - "available_four_stars": "Available 4-star Items", - "available_three_stars": "Available 3-star Items", - "template_missing": "data/gacha_details.html is missing." + "title": "祈願詳情", + "available_five_stars": "可獲得的5星物品", + "available_four_stars": "可獲得的4星物品", + "available_three_stars": "可獲得的3星物品", + "template_missing": "data/gacha_details.html 不存在。" } } } From a227b44c70244a6b1574881e6110e491c3a2b27c Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 13 May 2022 06:24:50 -0700 Subject: [PATCH 133/312] Fixed quests not finishing their questline --- .../java/emu/grasscutter/data/def/QuestData.java | 11 +++++++++++ .../emu/grasscutter/game/quest/GameMainQuest.java | 1 + .../java/emu/grasscutter/game/quest/GameQuest.java | 13 ++++++++++--- .../emu/grasscutter/game/quest/QuestManager.java | 2 +- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/main/java/emu/grasscutter/data/def/QuestData.java b/src/main/java/emu/grasscutter/data/def/QuestData.java index 31ac2ce7e..13e806dab 100644 --- a/src/main/java/emu/grasscutter/data/def/QuestData.java +++ b/src/main/java/emu/grasscutter/data/def/QuestData.java @@ -15,6 +15,9 @@ public class QuestData extends GameResource { private int Order; private long DescTextMapHash; + private boolean FinishParent; + private boolean IsRewind; + private LogicType AcceptCondComb; private QuestCondition[] acceptConditons; private LogicType FinishCondComb; @@ -45,6 +48,14 @@ public class QuestData extends GameResource { return DescTextMapHash; } + public boolean finishParent() { + return FinishParent; + } + + public boolean isRewind() { + return IsRewind; + } + public LogicType getAcceptCondComb() { return AcceptCondComb; } diff --git a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java index bf88b8efe..613819d0b 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java @@ -91,6 +91,7 @@ public class GameMainQuest { this.isFinished = true; this.state = ParentQuestState.PARENT_QUEST_STATE_FINISHED; this.getOwner().getSession().send(new PacketFinishedParentQuestUpdateNotify(this)); + this.save(); } public void save() { diff --git a/src/main/java/emu/grasscutter/game/quest/GameQuest.java b/src/main/java/emu/grasscutter/game/quest/GameQuest.java index b242166eb..5e1126fcb 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameQuest.java @@ -143,21 +143,28 @@ public class GameQuest { this.getOwner().getSession().send(new PacketQuestProgressUpdateNotify(this)); this.getOwner().getSession().send(new PacketQuestListUpdateNotify(this)); - this.save(); - this.tryAcceptQuestLine(); + if (this.getData().finishParent()) { + // This quest finishes the questline - the main quest will also save the quest to db so we dont have to call save() here + this.getMainQuest().finish(); + } else { + // Try and accept other quests if possible + this.tryAcceptQuestLine(); + this.save(); + } } public boolean tryAcceptQuestLine() { try { MainQuestData questConfig = GameData.getMainQuestDataMap().get(this.getMainQuestId()); + for (SubQuestData subQuest : questConfig.getSubQuests()) { GameQuest quest = getMainQuest().getChildQuestById(subQuest.getSubId()); if (quest == null) { QuestData questData = GameData.getQuestDataMap().get(subQuest.getSubId()); - if (questData == null) { + if (questData == null || questData.getAcceptCond() == null) { continue; } diff --git a/src/main/java/emu/grasscutter/game/quest/QuestManager.java b/src/main/java/emu/grasscutter/game/quest/QuestManager.java index 548e8241a..745ce9ef8 100644 --- a/src/main/java/emu/grasscutter/game/quest/QuestManager.java +++ b/src/main/java/emu/grasscutter/game/quest/QuestManager.java @@ -128,7 +128,7 @@ public class QuestManager { QuestData data = quest.getData(); for (int i = 0; i < data.getFinishCond().length; i++) { - if (quest.getFinishProgressList()[i] == 1) { + if (quest.getFinishProgressList() == null || quest.getFinishProgressList()[i] == 1) { continue; } From 83f8bec9470b163f01ced39dbf6b3dd14fe8e0ca Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 13 May 2022 06:33:12 -0700 Subject: [PATCH 134/312] Implement QUEST_CONTENT_FINISH_PLOT --- .../game/quest/content/ContentFinishPlot.java | 17 +++++++++++++++++ .../server/packet/recv/HandlerNpcTalkReq.java | 2 ++ 2 files changed, 19 insertions(+) create mode 100644 src/main/java/emu/grasscutter/game/quest/content/ContentFinishPlot.java diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentFinishPlot.java b/src/main/java/emu/grasscutter/game/quest/content/ContentFinishPlot.java new file mode 100644 index 000000000..d8e0cd4e5 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentFinishPlot.java @@ -0,0 +1,17 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.data.def.QuestData.QuestCondition; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_CONTENT_FINISH_PLOT) +public class ContentFinishPlot extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestCondition condition, int... params) { + return condition.getParam()[0] == params[0]; + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java index 82248c98c..3dae7fe10 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java @@ -16,7 +16,9 @@ public class HandlerNpcTalkReq extends PacketHandler { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { NpcTalkReq req = NpcTalkReq.parseFrom(payload); + // Why are there 2 quest triggers that do the same thing... session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_COMPLETE_TALK, req.getTalkId()); + session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_FINISH_PLOT, req.getTalkId()); session.send(new PacketNpcTalkRsp(req.getNpcEntityId(), req.getTalkId(), req.getEntityId())); } From e131c3c5ed61490f6006b82b631f21c18010f1a9 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Fri, 13 May 2022 11:37:17 -0400 Subject: [PATCH 135/312] Add `lombok` --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index 4434ed28e..2074ec553 100644 --- a/build.gradle +++ b/build.gradle @@ -86,6 +86,9 @@ dependencies { implementation group: 'org.luaj', name: 'luaj-jse', version: '3.0.1' protobuf files('proto/') + + compileOnly 'org.projectlombok:lombok:1.18.24' + annotationProcessor 'org.projectlombok:lombok:1.18.24' } configurations.all { From 0b21a439004312a4f89d308ee145dc815d5bc8b9 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Fri, 13 May 2022 11:37:49 -0400 Subject: [PATCH 136/312] Update project to `1.1.2-dev` --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2074ec553..68f6d449a 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,7 @@ sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 group = 'xyz.grasscutters' -version = '1.1.1-dev' +version = '1.1.2-dev' sourceCompatibility = 17 targetCompatibility = 17 From 39f23a0c47e97eb11c7a431d337178b0ee713949 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Fri, 13 May 2022 11:38:17 -0400 Subject: [PATCH 137/312] Add new authentication system --- .../auth/AuthenticationSystem.java | 101 +++++++++++ .../emu/grasscutter/auth/Authenticator.java | 17 ++ .../auth/DefaultAuthentication.java | 40 +++++ .../auth/DefaultAuthenticators.java | 161 ++++++++++++++++++ 4 files changed, 319 insertions(+) create mode 100644 src/main/java/emu/grasscutter/auth/AuthenticationSystem.java create mode 100644 src/main/java/emu/grasscutter/auth/Authenticator.java create mode 100644 src/main/java/emu/grasscutter/auth/DefaultAuthentication.java create mode 100644 src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java diff --git a/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java b/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java new file mode 100644 index 000000000..dae3402f2 --- /dev/null +++ b/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java @@ -0,0 +1,101 @@ +package emu.grasscutter.auth; + +import emu.grasscutter.server.http.objects.*; +import express.http.Request; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import javax.annotation.Nullable; + +/** + * Defines an authenticator for the server. + * Can be changed by plugins. + */ +public interface AuthenticationSystem { + + /** + * Called when a user requests to make an account. + * @param username The provided username. + * @param password The provided password. (SHA-256'ed) + */ + void createAccount(String username, String password); + + /** + * Called when a user requests to reset their password. + * @param username The username of the account to reset. + */ + void resetPassword(String username); + + /** + * This is the authenticator used for password authentication. + * @return An authenticator. + */ + Authenticator getPasswordAuthenticator(); + + /** + * This is the authenticator used for token authentication. + * @return An authenticator. + */ + Authenticator getTokenAuthenticator(); + + /** + * This is the authenticator used for session authentication. + * @return An authenticator. + */ + Authenticator getSessionKeyAuthenticator(); + + /** + * A data container that holds relevant data for authenticating a client. + * Call {@link AuthenticationRequest#builder()} to create a builder. + */ + @Builder @AllArgsConstructor @Getter + class AuthenticationRequest { + private final Request request; + @Nullable private final LoginAccountRequestJson passwordRequest; + @Nullable private final LoginTokenRequestJson tokenRequest; + @Nullable private final ComboTokenReqJson sessionKeyRequest; + @Nullable private final ComboTokenReqJson.LoginTokenData sessionKeyData; + } + + /** + * Generates an authentication request from a {@link LoginAccountRequestJson} object. + * @param request The Express request. + * @param jsonData The JSON data. + * @return An authentication request. + */ + static AuthenticationRequest fromPasswordRequest(Request request, LoginAccountRequestJson jsonData) { + return AuthenticationRequest.builder() + .request(request) + .passwordRequest(jsonData) + .build(); + } + + /** + * Generates an authentication request from a {@link LoginTokenRequestJson} object. + * @param request The Express request. + * @param jsonData The JSON data. + * @return An authentication request. + */ + static AuthenticationRequest fromTokenRequest(Request request, LoginTokenRequestJson jsonData) { + return AuthenticationRequest.builder() + .request(request) + .tokenRequest(jsonData) + .build(); + } + + /** + * Generates an authentication request from a {@link ComboTokenReqJson} object. + * @param request The Express request. + * @param jsonData The JSON data. + * @return An authentication request. + */ + static AuthenticationRequest fromComboTokenRequest(Request request, ComboTokenReqJson jsonData, + ComboTokenReqJson.LoginTokenData tokenData) { + return AuthenticationRequest.builder() + .request(request) + .sessionKeyRequest(jsonData) + .sessionKeyData(tokenData) + .build(); + } +} diff --git a/src/main/java/emu/grasscutter/auth/Authenticator.java b/src/main/java/emu/grasscutter/auth/Authenticator.java new file mode 100644 index 000000000..a5d756d8c --- /dev/null +++ b/src/main/java/emu/grasscutter/auth/Authenticator.java @@ -0,0 +1,17 @@ +package emu.grasscutter.auth; + +import emu.grasscutter.server.http.objects.*; + +/** + * Handles username/password authentication from the client. + * @param The response object type. Should be {@link LoginResultJson} or {@link ComboTokenResJson} + */ +public interface Authenticator { + + /** + * Attempt to authenticate the client with the provided credentials. + * @param request The authentication request wrapped in a {@link AuthenticationSystem.AuthenticationRequest} object. + * @return The result of the login in an object. + */ + T authenticate(AuthenticationSystem.AuthenticationRequest request); +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/auth/DefaultAuthentication.java b/src/main/java/emu/grasscutter/auth/DefaultAuthentication.java new file mode 100644 index 000000000..2864b80b5 --- /dev/null +++ b/src/main/java/emu/grasscutter/auth/DefaultAuthentication.java @@ -0,0 +1,40 @@ +package emu.grasscutter.auth; + +import emu.grasscutter.auth.DefaultAuthenticators.*; +import emu.grasscutter.server.http.objects.ComboTokenResJson; +import emu.grasscutter.server.http.objects.LoginResultJson; + +/** + * The default Grasscutter authentication implementation. + * Allows all users to access any account. + */ +public final class DefaultAuthentication implements AuthenticationSystem { + private final Authenticator passwordAuthenticator = new PasswordAuthenticator(); + private final Authenticator tokenAuthenticator = new TokenAuthenticator(); + private final Authenticator sessionKeyAuthenticator = new SessionKeyAuthenticator(); + + @Override + public void createAccount(String username, String password) { + // Unhandled. The default authenticator doesn't store passwords. + } + + @Override + public void resetPassword(String username) { + // Unhandled. The default authenticator doesn't store passwords. + } + + @Override + public Authenticator getPasswordAuthenticator() { + return this.passwordAuthenticator; + } + + @Override + public Authenticator getTokenAuthenticator() { + return this.tokenAuthenticator; + } + + @Override + public Authenticator getSessionKeyAuthenticator() { + return this.sessionKeyAuthenticator; + } +} diff --git a/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java b/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java new file mode 100644 index 000000000..298d24493 --- /dev/null +++ b/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java @@ -0,0 +1,161 @@ +package emu.grasscutter.auth; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.auth.AuthenticationSystem.AuthenticationRequest; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.Account; +import emu.grasscutter.server.http.objects.*; + +import static emu.grasscutter.Configuration.*; +import static emu.grasscutter.utils.Language.translate; + +/** + * A class containing default authenticators. + */ +public final class DefaultAuthenticators { + + /** + * Handles the authentication request from the username & password form. + */ + public static class PasswordAuthenticator implements Authenticator { + @Override public LoginResultJson authenticate(AuthenticationRequest request) { + var response = new LoginResultJson(); + + var requestData = request.getPasswordRequest(); + assert requestData != null; // This should never be null. + + boolean successfulLogin = false; + String address = request.getRequest().ip(); + String responseMessage = translate("messages.dispatch.account.username_error"); + + // Get account from database. + Account account = DatabaseHelper.getAccountByName(requestData.account); + + // Check if account exists. + if(account == null && ACCOUNT.autoCreate) { + // This account has been created AUTOMATICALLY. There will be no permissions added. + account = DatabaseHelper.createAccountWithId(requestData.account, 0); + + // Check if the account was created successfully. + if(account == null) { + responseMessage = translate("messages.dispatch.account.username_create_error"); + Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_error", address)); + } else { + // Add default permissions. + for (var permission : ACCOUNT.defaultPermissions) + account.addPermission(permission); + + // Continue with login. + successfulLogin = true; + + // Log the creation. + Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_success", address, response.data.account.uid)); + } + } else if(account != null) + successfulLogin = true; + + // Set response data. + if(successfulLogin) { + response.message = "OK"; + response.data.account.uid = account.getId(); + response.data.account.token = account.generateSessionKey(); + response.data.account.email = account.getEmail(); + + // Log the login. + Grasscutter.getLogger().info(translate("messages.dispatch.account.login_success", address, account.getId())); + } else { + response.retcode = -201; + response.message = responseMessage; + + // Log the failure. + Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_exist_error", address)); + } + + return response; + } + } + + /** + * Handles the authentication request from the game when using a registry token. + */ + public static class TokenAuthenticator implements Authenticator { + @Override public LoginResultJson authenticate(AuthenticationRequest request) { + var response = new LoginResultJson(); + + var requestData = request.getTokenRequest(); + assert requestData != null; + + boolean successfulLogin; + String address = request.getRequest().ip(); + + // Log the attempt. + Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_attempt", address)); + + // Get account from database. + Account account = DatabaseHelper.getAccountById(requestData.uid); + + // Check if account exists/token is valid. + successfulLogin = account != null && account.getSessionKey().equals(requestData.token); + + // Set response data. + if(successfulLogin) { + response.message = "OK"; + response.data.account.uid = account.getId(); + response.data.account.token = account.getSessionKey(); + response.data.account.email = account.getEmail(); + + // Log the login. + Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_success", address, requestData.uid)); + } else { + response.retcode = -201; + response.message = translate("messages.dispatch.account.account_cache_error"); + + // Log the failure. + Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_error", address)); + } + + return response; + } + } + + /** + * Handles the authentication request from the game when using a combo token/session key. + */ + public static class SessionKeyAuthenticator implements Authenticator { + @Override public ComboTokenResJson authenticate(AuthenticationRequest request) { + var response = new ComboTokenResJson(); + + var requestData = request.getSessionKeyRequest(); + var loginData = request.getSessionKeyData(); + assert requestData != null; assert loginData != null; + + boolean successfulLogin; + String address = request.getRequest().ip(); + + // Get account from database. + Account account = DatabaseHelper.getAccountById(loginData.uid); + + // Check if account exists/token is valid. + successfulLogin = account != null && account.getSessionKey().equals(loginData.token); + + // Set response data. + if(successfulLogin) { + response.message = "OK"; + response.data.open_id = account.getId(); + response.data.combo_id = "157795300"; + response.data.combo_token = account.generateLoginToken(); + + // Log the login. + Grasscutter.getLogger().info(translate("messages.dispatch.account.combo_token_success", address)); + } else { + response.retcode = -201; + response.message = translate("messages.dispatch.account.session_key_error"); + + // Log the failure. + Grasscutter.getLogger().info(translate("messages.dispatch.account.combo_token_error", address)); + } + + return response; + } + } +} From a0067b664eaf5d38342e757915dd4e87b4ce7bbe Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Fri, 13 May 2022 11:38:54 -0400 Subject: [PATCH 138/312] Add JSON-related methods to `Utils.java` --- .../java/emu/grasscutter/utils/Utils.java | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index 4af62bfb4..1fe026bd8 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -6,10 +6,7 @@ import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.time.*; import java.time.temporal.TemporalAdjusters; -import java.util.HashMap; -import java.util.Map; -import java.util.Random; -import java.util.Locale; +import java.util.*; import emu.grasscutter.Grasscutter; import io.netty.buffer.ByteBuf; @@ -308,10 +305,42 @@ public final class Utils { } /** - * get language code from Locale + * Gets the language code from a given locale. + * @param locale A locale. + * @return A string in the format of 'XX-XX'. */ public static String getLanguageCode(Locale locale) { return String.format("%s-%s", locale.getLanguage(), locale.getCountry()); } + /** + * Base64 encodes a given byte array. + * @param toEncode An array of bytes. + * @return A base64 encoded string. + */ + public static String base64Encode(byte[] toEncode) { + return Base64.getEncoder().encodeToString(toEncode); + } + + /** + * Base64 decodes a given string. + * @param toDecode A base64 encoded string. + * @return An array of bytes. + */ + public static byte[] base64Decode(String toDecode) { + return Base64.getDecoder().decode(toDecode); + } + + /** + * Safely JSON decodes a given string. + * @param jsonData The JSON-encoded data. + * @return JSON decoded data, or null if an exception occurred. + */ + public static T jsonDecode(String jsonData, Class classType) { + try { + return Grasscutter.getGsonFactory().fromJson(jsonData, classType); + } catch (Exception ignored) { + return null; + } + } } From 840f4706b5d7d39b2d6319a25c32ee7c522c5fa2 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Fri, 13 May 2022 11:39:40 -0400 Subject: [PATCH 139/312] Refactor dispatch (now called HTTP) server (pt. 1) --- .../java/emu/grasscutter/Configuration.java | 7 +- .../java/emu/grasscutter/Grasscutter.java | 150 +++++++++----- .../server/dispatch/DispatchServer.java | 20 +- .../grasscutter/server/http/HttpServer.java | 176 +++++++++++++++++ .../emu/grasscutter/server/http/Router.java | 16 ++ .../server/http/dispatch/DispatchHandler.java | 100 ++++++++++ .../server/http/dispatch/RegionHandler.java | 186 ++++++++++++++++++ .../http/handlers/AnnouncementsHandler.java | 58 ++++++ .../server/http/handlers/GenericHandler.java | 47 +++++ .../server/http/handlers/LogHandler.java | 18 ++ .../http/objects/ComboTokenReqJson.java | 15 ++ .../http/objects/ComboTokenResJson.java | 17 ++ .../http/objects/LoginAccountRequestJson.java | 7 + .../server/http/objects/LoginResultJson.java | 38 ++++ .../http/objects/LoginTokenRequestJson.java | 6 + .../grasscutter/utils/ConfigContainer.java | 31 ++- src/main/resources/languages/en-US.json | 3 +- 17 files changed, 828 insertions(+), 67 deletions(-) create mode 100644 src/main/java/emu/grasscutter/server/http/HttpServer.java create mode 100644 src/main/java/emu/grasscutter/server/http/Router.java create mode 100644 src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java create mode 100644 src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java create mode 100644 src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java create mode 100644 src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java create mode 100644 src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java create mode 100644 src/main/java/emu/grasscutter/server/http/objects/ComboTokenReqJson.java create mode 100644 src/main/java/emu/grasscutter/server/http/objects/ComboTokenResJson.java create mode 100644 src/main/java/emu/grasscutter/server/http/objects/LoginAccountRequestJson.java create mode 100644 src/main/java/emu/grasscutter/server/http/objects/LoginResultJson.java create mode 100644 src/main/java/emu/grasscutter/server/http/objects/LoginTokenRequestJson.java diff --git a/src/main/java/emu/grasscutter/Configuration.java b/src/main/java/emu/grasscutter/Configuration.java index 7adc334c1..52bfa65aa 100644 --- a/src/main/java/emu/grasscutter/Configuration.java +++ b/src/main/java/emu/grasscutter/Configuration.java @@ -34,11 +34,12 @@ public final class Configuration extends ConfigContainer { public static final Database DATABASE = config.databaseInfo; public static final Account ACCOUNT = config.account; - public static final Dispatch DISPATCH_INFO = config.server.dispatch; + public static final HTTP HTTP_INFO = config.server.http; public static final Game GAME_INFO = config.server.game; + public static final Dispatch DISPATCH_INFO = config.server.dispatch; - public static final Encryption DISPATCH_ENCRYPTION = config.server.dispatch.encryption; - public static final Policies DISPATCH_POLICIES = config.server.dispatch.policies; + public static final Encryption HTTP_ENCRYPTION = config.server.http.encryption; + public static final Policies HTTP_POLICIES = config.server.http.policies; public static final GameOptions GAME_OPTIONS = config.server.game.gameOptions; public static final GameOptions.InventoryLimits INVENTORY_LIMITS = config.server.game.gameOptions.inventoryLimits; diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 73e761e6e..bddfa9964 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -3,10 +3,18 @@ package emu.grasscutter; import java.io.*; import java.util.Calendar; +import emu.grasscutter.auth.AuthenticationSystem; +import emu.grasscutter.auth.DefaultAuthentication; import emu.grasscutter.command.CommandMap; import emu.grasscutter.plugin.PluginManager; import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.scripts.ScriptLoader; +import emu.grasscutter.server.http.HttpServer; +import emu.grasscutter.server.http.dispatch.DispatchHandler; +import emu.grasscutter.server.http.handlers.AnnouncementsHandler; +import emu.grasscutter.server.http.handlers.GenericHandler; +import emu.grasscutter.server.http.handlers.LogHandler; +import emu.grasscutter.server.http.dispatch.RegionHandler; import emu.grasscutter.utils.ConfigContainer; import emu.grasscutter.utils.Utils; import org.jline.reader.EndOfFileException; @@ -47,8 +55,10 @@ public final class Grasscutter { private static int day; // Current day of week. private static DispatchServer dispatchServer; + private static HttpServer httpServer; private static GameServer gameServer; private static PluginManager pluginManager; + private static AuthenticationSystem authenticationSystem; public static final Reflections reflector = new Reflections("emu.grasscutter"); public static ConfigContainer config; @@ -98,14 +108,27 @@ public final class Grasscutter { // Initialize database. DatabaseManager.initialize(); + + // Initialize the default authentication system. + authenticationSystem = new DefaultAuthentication(); // Create server instances. dispatchServer = new DispatchServer(); + httpServer = new HttpServer(); gameServer = new GameServer(); // Create a server hook instance with both servers. new ServerHook(gameServer, dispatchServer); + // Create plugin manager instance. pluginManager = new PluginManager(); + // Add HTTP routes after loading plugins. + httpServer.addRouter(HttpServer.UnhandledRequestRouter.class); + httpServer.addRouter(HttpServer.DefaultRequestRouter.class); + httpServer.addRouter(RegionHandler.class); + httpServer.addRouter(LogHandler.class); + httpServer.addRouter(GenericHandler.class); + httpServer.addRouter(AnnouncementsHandler.class); + httpServer.addRouter(DispatchHandler.class); // Start servers. var runMode = SERVER.runMode; @@ -114,6 +137,7 @@ public final class Grasscutter { gameServer.start(); } else if (runMode == ServerRunMode.DISPATCH_ONLY) { dispatchServer.start(); + httpServer.start(); } else if (runMode == ServerRunMode.GAME_ONLY) { gameServer.start(); } else { @@ -141,6 +165,19 @@ public final class Grasscutter { pluginManager.disablePlugins(); } + /* + * Methods for the language system component. + */ + + public static void loadLanguage() { + var locale = config.language.language; + language = Language.getLanguage(Utils.getLanguageCode(locale)); + } + + /* + * Methods for the configuration system component. + */ + /** * Attempts to load the configuration from a file. */ @@ -157,11 +194,6 @@ public final class Grasscutter { } } - public static void loadLanguage() { - var locale = config.language.language; - language = Language.getLanguage(Utils.getLanguageCode(locale)); - } - /** * Saves the provided server configuration. * @param config The configuration to save, or null for a new one. @@ -178,44 +210,10 @@ public final class Grasscutter { } } - public static void startConsole() { - // Console should not start in dispatch only mode. - if (SERVER.runMode == ServerRunMode.DISPATCH_ONLY) { - getLogger().info(translate("messages.dispatch.no_commands_error")); - return; - } - - getLogger().info(translate("messages.status.done")); - String input = null; - boolean isLastInterrupted = false; - while (true) { - try { - input = consoleLineReader.readLine("> "); - } catch (UserInterruptException e) { - if (!isLastInterrupted) { - isLastInterrupted = true; - Grasscutter.getLogger().info("Press Ctrl-C again to shutdown."); - continue; - } else { - Runtime.getRuntime().exit(0); - } - } catch (EndOfFileException e) { - Grasscutter.getLogger().info("EOF detected."); - continue; - } catch (IOError e) { - Grasscutter.getLogger().error("An IO error occurred.", e); - continue; - } - - isLastInterrupted = false; - try { - CommandMap.getInstance().invoke(null, null, input); - } catch (Exception e) { - Grasscutter.getLogger().error(translate("messages.game.command_error"), e); - } - } - } - + /* + * Getters for the various server components. + */ + public static ConfigContainer getConfig() { return config; } @@ -271,16 +269,74 @@ public final class Grasscutter { public static PluginManager getPluginManager() { return pluginManager; } - - public static void updateDayOfWeek() { - Calendar calendar = Calendar.getInstance(); - day = calendar.get(Calendar.DAY_OF_WEEK); + + public static AuthenticationSystem getAuthenticationSystem() { + return authenticationSystem; } public static int getCurrentDayOfWeek() { return day; } + + /* + * Utility methods. + */ + + public static void updateDayOfWeek() { + Calendar calendar = Calendar.getInstance(); + day = calendar.get(Calendar.DAY_OF_WEEK); + } + public static void startConsole() { + // Console should not start in dispatch only mode. + if (SERVER.runMode == ServerRunMode.DISPATCH_ONLY) { + getLogger().info(translate("messages.dispatch.no_commands_error")); + return; + } + + getLogger().info(translate("messages.status.done")); + String input = null; + boolean isLastInterrupted = false; + while (true) { + try { + input = consoleLineReader.readLine("> "); + } catch (UserInterruptException e) { + if (!isLastInterrupted) { + isLastInterrupted = true; + Grasscutter.getLogger().info("Press Ctrl-C again to shutdown."); + continue; + } else { + Runtime.getRuntime().exit(0); + } + } catch (EndOfFileException e) { + Grasscutter.getLogger().info("EOF detected."); + continue; + } catch (IOError e) { + Grasscutter.getLogger().error("An IO error occurred.", e); + continue; + } + + isLastInterrupted = false; + try { + CommandMap.getInstance().invoke(null, null, input); + } catch (Exception e) { + Grasscutter.getLogger().error(translate("messages.game.command_error"), e); + } + } + } + + /** + * Sets the authentication system for the server. + * @param authenticationSystem The authentication system to use. + */ + public static void setAuthenticationSystem(AuthenticationSystem authenticationSystem) { + Grasscutter.authenticationSystem = authenticationSystem; + } + + /* + * Enums for the configuration. + */ + public enum ServerRunMode { HYBRID, DISPATCH_ONLY, GAME_ONLY } diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index 4e09f8881..8b8a9a185 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -117,7 +117,7 @@ public final class DispatchServer { .setTitle(DISPATCH_INFO.defaultName) .setType("DEV_PUBLIC") .setDispatchUrl( - "http" + (DISPATCH_ENCRYPTION.useInRouting ? "s" : "") + "://" + "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://" + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + "/query_cur_region/" + defaultServerName) @@ -150,7 +150,7 @@ public final class DispatchServer { .setTitle(regionInfo.Title) .setType("DEV_PUBLIC") .setDispatchUrl( - "http" + (DISPATCH_ENCRYPTION.useInRouting ? "s" : "") + "://" + "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://" + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + "/query_cur_region/" + regionInfo.Name) @@ -189,14 +189,14 @@ public final class DispatchServer { Server server = new Server(); ServerConnector serverConnector; - if(DISPATCH_ENCRYPTION.useEncryption) { + if(HTTP_ENCRYPTION.useEncryption) { SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); - File keystoreFile = new File(DISPATCH_ENCRYPTION.keystore); + File keystoreFile = new File(HTTP_ENCRYPTION.keystore); if(keystoreFile.exists()) { try { sslContextFactory.setKeyStorePath(keystoreFile.getPath()); - sslContextFactory.setKeyStorePassword(DISPATCH_ENCRYPTION.keystorePassword); + sslContextFactory.setKeyStorePassword(HTTP_ENCRYPTION.keystorePassword); } catch (Exception e) { e.printStackTrace(); Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.password_error")); @@ -214,7 +214,7 @@ public final class DispatchServer { serverConnector = new ServerConnector(server, sslContextFactory); } else { Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.no_keystore_error")); - DISPATCH_ENCRYPTION.useEncryption = false; + HTTP_ENCRYPTION.useEncryption = false; serverConnector = new ServerConnector(server); } @@ -227,18 +227,19 @@ public final class DispatchServer { return server; }); - config.enforceSsl = DISPATCH_ENCRYPTION.useEncryption; + config.enforceSsl = HTTP_ENCRYPTION.useEncryption; if(SERVER.debugLevel == ServerDebugMode.ALL) { config.enableDevLogging(); } - if (DISPATCH_POLICIES.cors.enabled) { - var corsPolicy = DISPATCH_POLICIES.cors; + if (HTTP_POLICIES.cors.enabled) { + var corsPolicy = HTTP_POLICIES.cors; if (corsPolicy.allowedOrigins.length > 0) config.enableCorsForOrigin(corsPolicy.allowedOrigins); else config.enableCorsForAllOrigins(); } }); + httpServer.get("/", (req, res) -> res.send("" + translate("messages.status.welcome") + "")); httpServer.raw().error(404, ctx -> { @@ -279,6 +280,7 @@ public final class DispatchServer { res.send(event.getRegionList()); }); + // /server/:id -> 2.6.5x httpServer.get("/query_cur_region/:id", (req, res) -> { String regionName = req.params("id"); // Log diff --git a/src/main/java/emu/grasscutter/server/http/HttpServer.java b/src/main/java/emu/grasscutter/server/http/HttpServer.java new file mode 100644 index 000000000..dc0d396a6 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/HttpServer.java @@ -0,0 +1,176 @@ +package emu.grasscutter.server.http; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.Grasscutter.ServerDebugMode; +import express.Express; +import io.javalin.Javalin; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import java.io.File; + +import static emu.grasscutter.Configuration.*; +import static emu.grasscutter.utils.Language.translate; + +/** + * Manages all HTTP-related classes. + * (including dispatch, announcements, gacha, etc.) + */ +public final class HttpServer { + private final Express express; + + /** + * Configures the Express application. + */ + public HttpServer() { + this.express = new Express(config -> { + // Set the Express HTTP server. + config.server(HttpServer::createServer); + + // Configure encryption/HTTPS/SSL. + config.enforceSsl = HTTP_ENCRYPTION.useEncryption; + + // Configure HTTP policies. + if(HTTP_POLICIES.cors.enabled) { + var allowedOrigins = HTTP_POLICIES.cors.allowedOrigins; + if (allowedOrigins.length > 0) + config.enableCorsForOrigin(allowedOrigins); + else config.enableCorsForAllOrigins(); + } + + // Configure debug logging. + if(SERVER.debugLevel == ServerDebugMode.ALL) + config.enableDevLogging(); + + // Disable compression on static files. + config.precompressStaticFiles = false; + }); + } + + /** + * Creates an HTTP(S) server. + * @return A server instance. + */ + @SuppressWarnings("resource") + private static Server createServer() { + Server server = new Server(); + ServerConnector serverConnector + = new ServerConnector(server); + + if(HTTP_ENCRYPTION.useEncryption) { + var sslContextFactory = new SslContextFactory.Server(); + var keystoreFile = new File(HTTP_ENCRYPTION.keystore); + + if(!keystoreFile.exists()) {; + HTTP_ENCRYPTION.useEncryption = false; + HTTP_ENCRYPTION.useInRouting = false; + + Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.no_keystore_error")); + } else try { + sslContextFactory.setKeyStorePath(keystoreFile.getPath()); + sslContextFactory.setKeyStorePassword(HTTP_ENCRYPTION.keystorePassword); + } catch (Exception ignored) { + Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.password_error")); + + try { + sslContextFactory.setKeyStorePath(keystoreFile.getPath()); + sslContextFactory.setKeyStorePassword("123456"); + + Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.default_password")); + } catch (Exception exception) { + Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.general_error"), exception); + } + } finally { + serverConnector = new ServerConnector(server, sslContextFactory); + } + } + + serverConnector.setPort(HTTP_INFO.bindPort); + server.setConnectors(new ServerConnector[]{serverConnector}); + + return server; + } + + /** + * Returns the handle for the Express application. + * @return A Javalin instance. + */ + public Javalin getHandle() { + return this.express.raw(); + } + + /** + * Initializes the provided class. + * @param router The router class. + * @return Method chaining. + */ + @SuppressWarnings("UnusedReturnValue") + public HttpServer addRouter(Class router, Object... args) { + // Get all constructor parameters. + Class[] types = new Class[args.length]; + for(var argument : args) + types[args.length - 1] = argument.getClass(); + + try { // Create a router instance & apply routes. + var constructor = router.getDeclaredConstructor(types); // Get the constructor. + var routerInstance = constructor.newInstance(args); // Create instance. + routerInstance.applyRoutes(this.express, this.getHandle()); // Apply routes. + } catch (Exception exception) { + Grasscutter.getLogger().warn(translate("messages.dispatch.router_error"), exception); + } return this; + } + + /** + * Starts listening on the HTTP server. + */ + public void start() { + // Attempt to start the HTTP server. + this.express.listen(HTTP_INFO.bindAddress, HTTP_INFO.bindPort); + + // Log bind information. + Grasscutter.getLogger().info(translate("messages.dispatch.port_bind", Integer.toString(this.express.raw().port()))); + } + + /** + * Handles the '/' (index) endpoint on the Express application. + */ + public static class DefaultRequestRouter implements Router { + @Override public void applyRoutes(Express express, Javalin handle) { + express.get("/", (req, res) -> res.send(""" + + + + + + %s + + """.formatted(translate("messages.status.welcome")))); + } + } + + /** + * Handles unhandled endpoints on the Express application. + */ + public static class UnhandledRequestRouter implements Router { + @Override public void applyRoutes(Express express, Javalin handle) { + handle.error(404, context -> { + if(SERVER.debugLevel == ServerDebugMode.MISSING) + Grasscutter.getLogger().info(translate("messages.dispatch.unhandled_request_error", context.method(), context.url())); + context.contentType("text/html"); + context.result(""" + + + + + + + + + + + """); + }); + } + } +} diff --git a/src/main/java/emu/grasscutter/server/http/Router.java b/src/main/java/emu/grasscutter/server/http/Router.java new file mode 100644 index 000000000..1720d7ca0 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/Router.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.http; + +import express.Express; +import io.javalin.Javalin; + +/** + * Defines routes for an {@link Express} instance. + */ +public interface Router { + + /** + * Called when the router is initialized by Express. + * @param express An Express instance. + */ + void applyRoutes(Express express, Javalin handle); +} diff --git a/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java b/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java new file mode 100644 index 000000000..22a31fe6a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java @@ -0,0 +1,100 @@ +package emu.grasscutter.server.http.dispatch; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.auth.AuthenticationSystem; +import emu.grasscutter.server.http.Router; +import emu.grasscutter.server.http.objects.*; +import emu.grasscutter.server.http.objects.ComboTokenReqJson.LoginTokenData; +import emu.grasscutter.utils.Utils; +import express.Express; +import express.http.Request; +import express.http.Response; +import io.javalin.Javalin; + +import static emu.grasscutter.utils.Language.translate; + +/** + * Handles requests related to authentication. (aka dispatch) + */ +public final class DispatchHandler implements Router { + @Override public void applyRoutes(Express express, Javalin handle) { + // Username & Password login (from client). + express.post("/hk4e_global/mdk/shield/api/login", DispatchHandler::clientLogin); + // Cached token login (from registry). + express.post("/hk4e_global/mdk/shield/api/verify", DispatchHandler::tokenLogin); + // Combo token login (from session key). + express.post("/hk4e_global/combo/granter/login/v2/login", DispatchHandler::sessionKeyLogin); + } + + /** + * @route /hk4e_global/mdk/shield/api/login + */ + private static void clientLogin(Request request, Response response) { + // Parse body data. + String rawBodyData = request.ctx().body(); + var bodyData = Utils.jsonDecode(rawBodyData, LoginAccountRequestJson.class); + + // Validate body data. + if(bodyData == null) + return; + + // Pass data to authentication handler. + var responseData = Grasscutter.getAuthenticationSystem() + .getPasswordAuthenticator() + .authenticate(AuthenticationSystem.fromPasswordRequest(request, bodyData)); + // Send response. + response.send(responseData); + + // Log to console. + Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", request.ip())); + } + + /** + * @route /hk4e_global/mdk/shield/api/verify + */ + private static void tokenLogin(Request request, Response response) { + // Parse body data. + String rawBodyData = request.ctx().body(); + var bodyData = Utils.jsonDecode(rawBodyData, LoginTokenRequestJson.class); + + // Validate body data. + if(bodyData == null) + return; + + // Pass data to authentication handler. + var responseData = Grasscutter.getAuthenticationSystem() + .getTokenAuthenticator() + .authenticate(AuthenticationSystem.fromTokenRequest(request, bodyData)); + // Send response. + response.send(responseData); + + // Log to console. + Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", request.ip())); + } + + /** + * @route /hk4e_global/combo/granter/login/v2/login + */ + private static void sessionKeyLogin(Request request, Response response) { + // Parse body data. + String rawBodyData = request.ctx().body(); + var bodyData = Utils.jsonDecode(rawBodyData, ComboTokenReqJson.class); + + // Validate body data. + if(bodyData == null || bodyData.data == null) + return; + + // Decode additional body data. + var tokenData = Utils.jsonDecode(bodyData.data, LoginTokenData.class); + + // Pass data to authentication handler. + var responseData = Grasscutter.getAuthenticationSystem() + .getSessionKeyAuthenticator() + .authenticate(AuthenticationSystem.fromComboTokenRequest(request, bodyData, tokenData)); + // Send response. + response.send(responseData); + + // Log to console. + Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", request.ip())); + } +} diff --git a/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java b/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java new file mode 100644 index 000000000..e720a4b15 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java @@ -0,0 +1,186 @@ +package emu.grasscutter.server.http.dispatch; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.Grasscutter.ServerRunMode; +import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.*; +import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo; +import emu.grasscutter.server.event.dispatch.QueryAllRegionsEvent; +import emu.grasscutter.server.event.dispatch.QueryCurrentRegionEvent; +import emu.grasscutter.server.http.Router; +import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.Utils; +import express.Express; +import express.http.Request; +import express.http.Response; +import io.javalin.Javalin; + +import java.io.File; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static emu.grasscutter.Configuration.*; +import static emu.grasscutter.net.proto.QueryRegionListHttpRspOuterClass.*; + +/** + * Handles requests related to region queries. + */ +public final class RegionHandler implements Router { + private String regionQuery = ""; + private String regionList = ""; + + private static final Map regions = new ConcurrentHashMap<>(); + private static String regionListResponse; + + public RegionHandler() { + try { // Read & initialize region data. + this.readRegionData(); + this.initialize(); + } catch (Exception exception) { + Grasscutter.getLogger().error("Failed to initialize region data.", exception); + } + } + + /** + * Loads initial region data. + */ + private void readRegionData() { + File file; + + file = new File(DATA("query_region_list.txt")); + if (file.exists()) + this.regionList = new String(FileUtils.read(file)); + else Grasscutter.getLogger().error("[Dispatch] 'query_region_list' not found!"); + + file = new File(DATA("query_cur_region.txt")); + if (file.exists()) + regionQuery = new String(FileUtils.read(file)); + else Grasscutter.getLogger().warn("[Dispatch] 'query_cur_region' not found!"); + } + + /** + * Configures region data according to configuration. + */ + private void initialize() throws InvalidProtocolBufferException { + // Decode the initial region query. + byte[] queryBase64 = Base64.getDecoder().decode(this.regionQuery); + QueryCurrRegionHttpRsp regionQuery = QueryCurrRegionHttpRsp.parseFrom(queryBase64); + + // Create regions. + List servers = new ArrayList<>(); + List usedNames = new ArrayList<>(); // List to check for potential naming conflicts. + + var configuredRegions = new ArrayList<>(List.of(DISPATCH_INFO.regions)); + if(SERVER.runMode != ServerRunMode.HYBRID && configuredRegions.size() == 0) { + Grasscutter.getLogger().error("[Dispatch] There are no game servers available. Exiting due to unplayable state."); + System.exit(1); + } else configuredRegions.add(new Region("os_usa", DISPATCH_INFO.defaultName, + lr(GAME_INFO.accessAddress, GAME_INFO.bindAddress), + lr(GAME_INFO.accessPort, GAME_INFO.bindPort))); + + configuredRegions.forEach(region -> { + if (usedNames.contains(region.Name)) { + Grasscutter.getLogger().error("Region name already in use."); + return; + } + + // Create a region identifier. + var identifier = RegionSimpleInfo.newBuilder() + .setName(region.Name).setTitle(region.Title) + .setType("DEV_PUBLIC").setDispatchUrl( + "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://" + + lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":" + + lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort) + + "/query_cur_region/" + region.Name) + .build(); + usedNames.add(region.Name); servers.add(identifier); + + // Create a region info object. + var regionInfo = regionQuery.getRegionInfo().toBuilder() + .setGateserverIp(region.Ip).setGateserverPort(region.Port) + .setSecretKey(ByteString.copyFrom(FileUtils.read(KEYS_FOLDER + "/dispatchSeed.bin"))) + .build(); + // Create an updated region query. + var updatedQuery = regionQuery.toBuilder().setRegionInfo(regionInfo).build(); + regions.put(region.Name, new RegionData(updatedQuery, Utils.base64Encode(updatedQuery.toByteString().toByteArray()))); + }); + + // Decode the initial region list. + byte[] listBase64 = Base64.getDecoder().decode(this.regionList); + QueryRegionListHttpRsp regionList = QueryRegionListHttpRsp.parseFrom(listBase64); + + // Create an updated region list. + QueryRegionListHttpRsp updatedRegionList = QueryRegionListHttpRsp.newBuilder() + .addAllRegionList(servers) + .setClientSecretKey(regionList.getClientSecretKey()) + .setClientCustomConfigEncrypted(regionList.getClientCustomConfigEncrypted()) + .setEnableLoginPc(true).build(); + + // Set the region list response. + regionListResponse = Utils.base64Encode(updatedRegionList.toByteString().toByteArray()); + } + + @Override public void applyRoutes(Express express, Javalin handle) { + express.get("/query_region_list", RegionHandler::queryRegionList); + express.get("/query_cur_region/:region", RegionHandler::queryCurrentRegion ); + } + + /** + * @route /query_region_list + */ + private static void queryRegionList(Request request, Response response) { + // Invoke event. + QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponse); event.call(); + // Respond with event result. + response.send(event.getRegionList()); + + // Log to console. + Grasscutter.getLogger().info(String.format("[Dispatch] Client %s request: query_region_list", request.ip())); + } + + /** + * @route /query_cur_region/:region + */ + private static void queryCurrentRegion(Request request, Response response) { + // Get region to query. + String regionName = request.params("region"); + + // Get region data. + String regionData = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="; + if (request.query().values().size() > 0) + regionData = regions.get(regionName).getBase64(); + + // Invoke event. + QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); event.call(); + // Respond with event result. + response.send(event.getRegionInfo()); + + // Log to console. + Grasscutter.getLogger().info(String.format("Client %s request: query_cur_region/%s", request.ip(), regionName)); + } + + /** + * Region data container. + */ + public static class RegionData { + private final QueryCurrRegionHttpRsp regionQuery; + private final String base64; + + public RegionData(QueryCurrRegionHttpRsp prq, String b64) { + this.regionQuery = prq; + this.base64 = b64; + } + + public QueryCurrRegionHttpRsp getRegionQuery() { + return this.regionQuery; + } + + public String getBase64() { + return this.base64; + } + } +} diff --git a/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java new file mode 100644 index 000000000..a64e0552a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java @@ -0,0 +1,58 @@ +package emu.grasscutter.server.http.handlers; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.server.dispatch.DispatchHttpJsonHandler; +import emu.grasscutter.server.http.Router; +import express.Express; +import express.http.Request; +import express.http.Response; +import io.javalin.Javalin; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Objects; + +import static emu.grasscutter.Configuration.DATA; + +/** + * Handles requests related to the announcements page. + */ +public final class AnnouncementsHandler implements Router { + @Override public void applyRoutes(Express express, Javalin handle) { + // hk4e-api-os.hoyoverse.com + express.all("/common/hk4e_global/announcement/api/getAlertPic", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}")); + // hk4e-api-os.hoyoverse.com + express.all("/common/hk4e_global/announcement/api/getAlertAnn", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}")); + // hk4e-api-os.hoyoverse.com + express.all("/common/hk4e_global/announcement/api/getAnnList", AnnouncementsHandler::getAnnouncement); + // hk4e-api-os-static.hoyoverse.com + express.all("/common/hk4e_global/announcement/api/getAnnContent", AnnouncementsHandler::getAnnouncement); + // hk4e-sdk-os.hoyoverse.com + express.all("/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}")); + } + + private static void getAnnouncement(Request request, Response response) { + if (Objects.equals(request.baseUrl(), "/common/hk4e_global/announcement/api/getAnnContent")) { + response.send("{\"retcode\":0,\"message\":\"OK\",\"data\":" + readToString(new File(DATA("GameAnnouncement.json"))) +"}"); + } else if (Objects.equals(request.baseUrl(), "/common/hk4e_global/announcement/api/getAnnList")) { + String data = readToString(new File(DATA("GameAnnouncementList.json"))).replace("System.currentTimeMillis()",String.valueOf(System.currentTimeMillis())); + response.send("{\"retcode\":0,\"message\":\"OK\",\"data\": "+data +"}"); + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private static String readToString(File file) { + long length = file.length(); + byte[] content = new byte[(int) length]; + + try { + FileInputStream in = new FileInputStream(file); + in.read(content); in.close(); + } catch (IOException ignored) { + Grasscutter.getLogger().warn("File not found: " + file.getAbsolutePath()); + } + + return new String(content); + } +} diff --git a/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java new file mode 100644 index 000000000..bb0bc8eea --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java @@ -0,0 +1,47 @@ +package emu.grasscutter.server.http.handlers; + +import emu.grasscutter.server.dispatch.DispatchHttpJsonHandler; +import emu.grasscutter.server.http.Router; +import express.Express; +import io.javalin.Javalin; + +/** + * Handles all generic, hard-coded responses. + */ +public final class GenericHandler implements Router { + @Override public void applyRoutes(Express express, Javalin handle) { + // hk4e-sdk-os.hoyoverse.com + express.get("/hk4e_global/mdk/agreement/api/getAgreementInfos", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"marketing_agreements\":[]}}")); + // hk4e-sdk-os.hoyoverse.com + // this could be either GET or POST based on the observation of different clients + express.all("/hk4e_global/combo/granter/api/compareProtocolVersion", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"modified\":true,\"protocol\":{\"id\":0,\"app_id\":4,\"language\":\"en\",\"user_proto\":\"\",\"priv_proto\":\"\",\"major\":7,\"minimum\":0,\"create_time\":\"0\",\"teenager_proto\":\"\",\"third_proto\":\"\"}}}")); + + // api-account-os.hoyoverse.com + express.post("/account/risky/api/check", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"none\",\"action\":\"ACTION_NONE\",\"geetest\":null}}")); + + // sdk-os-static.hoyoverse.com + express.get("/combo/box/api/config/sdk/combo", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"vals\":{\"disable_email_bind_skip\":\"false\",\"email_bind_remind_interval\":\"7\",\"email_bind_remind\":\"true\"}}}")); + // hk4e-sdk-os-static.hoyoverse.com + express.get("/hk4e_global/combo/granter/api/getConfig", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"protocol\":true,\"qr_enabled\":false,\"log_level\":\"INFO\",\"announce_url\":\"https://webstatic-sea.hoyoverse.com/hk4e/announcement/index.html?sdk_presentation_style=fullscreen\\u0026sdk_screen_transparent=true\\u0026game_biz=hk4e_global\\u0026auth_appid=announcement\\u0026game=hk4e#/\",\"push_alias_type\":2,\"disable_ysdk_guard\":false,\"enable_announce_pic_popup\":true}}")); + // hk4e-sdk-os-static.hoyoverse.com + express.get("/hk4e_global/mdk/shield/api/loadConfig", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":6,\"game_key\":\"hk4e_global\",\"client\":\"PC\",\"identity\":\"I_IDENTITY\",\"guest\":false,\"ignore_versions\":\"\",\"scene\":\"S_NORMAL\",\"name\":\"原神海外\",\"disable_regist\":false,\"enable_email_captcha\":false,\"thirdparty\":[\"fb\",\"tw\"],\"disable_mmt\":false,\"server_guest\":false,\"thirdparty_ignore\":{\"tw\":\"\",\"fb\":\"\"},\"enable_ps_bind_account\":false,\"thirdparty_login_configs\":{\"tw\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800},\"fb\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800}}}}")); + // Test api? + // abtest-api-data-sg.hoyoverse.com + express.post("/data_abtest_api/config/experiment/list", new DispatchHttpJsonHandler("{\"retcode\":0,\"success\":true,\"message\":\"\",\"data\":[{\"code\":1000,\"type\":2,\"config_id\":\"14\",\"period_id\":\"6036_99\",\"version\":\"1\",\"configs\":{\"cardType\":\"old\"}}]}")); + + // hk4e-api-os.hoyoverse.com + express.all("/common/hk4e_global/announcement/api/getAlertPic", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}")); + // hk4e-api-os.hoyoverse.com + express.all("/common/hk4e_global/announcement/api/getAlertAnn", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}")); + + // log-upload-os.mihoyo.com + express.all("/log/sdk/upload", new DispatchHttpJsonHandler("{\"code\":0}")); + express.all("/sdk/upload", new DispatchHttpJsonHandler("{\"code\":0}")); + express.post("/sdk/dataUpload", new DispatchHttpJsonHandler("{\"code\":0}")); + // /perf/config/verify?device_id=xxx&platform=x&name=xxx + express.all("/perf/config/verify", new DispatchHttpJsonHandler("{\"code\":0}")); + + // webstatic-sea.hoyoverse.com + express.get("/admin/mi18n/plat_oversea/m202003048/m202003048-version.json", new DispatchHttpJsonHandler("{\"version\":51}")); + } +} diff --git a/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java new file mode 100644 index 000000000..4f52c0826 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.http.handlers; + +import emu.grasscutter.server.dispatch.ClientLogHandler; +import emu.grasscutter.server.http.Router; +import express.Express; +import io.javalin.Javalin; + +/** + * Handles logging requests made to the server. + */ +public final class LogHandler implements Router { + @Override public void applyRoutes(Express express, Javalin handle) { + // overseauspider.yuanshen.com + express.post("/log", new ClientLogHandler()); + // log-upload-os.mihoyo.com + express.post("/crash/dataUpload", new ClientLogHandler()); + } +} diff --git a/src/main/java/emu/grasscutter/server/http/objects/ComboTokenReqJson.java b/src/main/java/emu/grasscutter/server/http/objects/ComboTokenReqJson.java new file mode 100644 index 000000000..5642f159a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/objects/ComboTokenReqJson.java @@ -0,0 +1,15 @@ +package emu.grasscutter.server.http.objects; + +public class ComboTokenReqJson { + public int app_id; + public int channel_id; + public String data; + public String device; + public String sign; + + public static class LoginTokenData { + public String uid; + public String token; + public boolean guest; + } +} diff --git a/src/main/java/emu/grasscutter/server/http/objects/ComboTokenResJson.java b/src/main/java/emu/grasscutter/server/http/objects/ComboTokenResJson.java new file mode 100644 index 000000000..b592fa163 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/objects/ComboTokenResJson.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.http.objects; + +public class ComboTokenResJson { + public String message; + public int retcode; + public LoginData data = new LoginData(); + + public static class LoginData { + public int account_type = 1; + public boolean heartbeat; + public String combo_id; + public String combo_token; + public String open_id; + public String data = "{\"guest\":false}"; + public String fatigue_remind = null; // ? + } +} diff --git a/src/main/java/emu/grasscutter/server/http/objects/LoginAccountRequestJson.java b/src/main/java/emu/grasscutter/server/http/objects/LoginAccountRequestJson.java new file mode 100644 index 000000000..3a8193a97 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/objects/LoginAccountRequestJson.java @@ -0,0 +1,7 @@ +package emu.grasscutter.server.http.objects; + +public class LoginAccountRequestJson { + public String account; + public String password; + public boolean is_crypto; +} diff --git a/src/main/java/emu/grasscutter/server/http/objects/LoginResultJson.java b/src/main/java/emu/grasscutter/server/http/objects/LoginResultJson.java new file mode 100644 index 000000000..5601c1c29 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/objects/LoginResultJson.java @@ -0,0 +1,38 @@ +package emu.grasscutter.server.http.objects; + +public class LoginResultJson { + public String message; + public int retcode; + public VerifyData data = new VerifyData(); + + public static class VerifyData { + public VerifyAccountData account = new VerifyAccountData(); + public boolean device_grant_required = false; + public String realname_operation = "NONE"; + public boolean realperson_required = false; + public boolean safe_mobile_required = false; + } + + public static class VerifyAccountData { + public String uid; + public String name = ""; + public String email = ""; + public String mobile = ""; + public String is_email_verify = "0"; + public String realname = ""; + public String identity_card = ""; + public String token; + public String safe_mobile = ""; + public String facebook_name = ""; + public String twitter_name = ""; + public String game_center_name = ""; + public String google_name = ""; + public String apple_name = ""; + public String sony_name = ""; + public String tap_name = ""; + public String country = "US"; + public String reactivate_ticket = ""; + public String area_code = "**"; + public String device_grant_ticket = ""; + } +} diff --git a/src/main/java/emu/grasscutter/server/http/objects/LoginTokenRequestJson.java b/src/main/java/emu/grasscutter/server/http/objects/LoginTokenRequestJson.java new file mode 100644 index 000000000..d01c60401 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/objects/LoginTokenRequestJson.java @@ -0,0 +1,6 @@ +package emu.grasscutter.server.http.objects; + +public class LoginTokenRequestJson { + public String uid; + public String token; +} diff --git a/src/main/java/emu/grasscutter/utils/ConfigContainer.java b/src/main/java/emu/grasscutter/utils/ConfigContainer.java index 76556700c..5a06b90be 100644 --- a/src/main/java/emu/grasscutter/utils/ConfigContainer.java +++ b/src/main/java/emu/grasscutter/utils/ConfigContainer.java @@ -96,8 +96,10 @@ public class ConfigContainer { public ServerDebugMode debugLevel = ServerDebugMode.NONE; public ServerRunMode runMode = ServerRunMode.HYBRID; - public Dispatch dispatch = new Dispatch(); + public HTTP http = new HTTP(); public Game game = new Game(); + + public Dispatch dispatch = new Dispatch(); } public static class Language { @@ -111,8 +113,8 @@ public class ConfigContainer { } /* Server options. */ - - public static class Dispatch { + + public static class HTTP { public String bindAddress = "0.0.0.0"; /* This is the address used in URLs. */ public String accessAddress = "127.0.0.1"; @@ -120,12 +122,9 @@ public class ConfigContainer { public int bindPort = 443; /* This is the port used in URLs. */ public int accessPort = 0; - + public Encryption encryption = new Encryption(); public Policies policies = new Policies(); - public Region[] regions = {}; - - public String defaultName = "Grasscutter"; } public static class Game { @@ -144,6 +143,12 @@ public class ConfigContainer { /* Data containers. */ + public static class Dispatch { + public Region[] regions = {}; + + public String defaultName = "Grasscutter"; + } + public static class Encryption { public boolean useEncryption = true; /* Should 'https' be appended to URLs? */ @@ -226,6 +231,18 @@ public class ConfigContainer { /* Objects. */ public static class Region { + public Region() { } + + public Region( + String name, String title, + String address, int port + ) { + this.Name = name; + this.Title = title; + this.Ip = address; + this.Port = port; + } + public String Name = "os_usa"; public String Title = "Grasscutter"; public String Ip = "127.0.0.1"; diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index c9c3c0c70..b23f2913d 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -33,7 +33,8 @@ "session_key_error": "Wrong session key.", "username_error": "Username not found.", "username_create_error": "Username not found, create failed." - } + }, + "router_error": "[Dispatch] Unable to attach router." }, "status": { "free_software": "Grasscutter is FREE software. If you have paid for this, you may have been scammed. Homepage: https://github.com/Grasscutters/Grasscutter", From 19ee983c086042042f17843211b5cc7abc765bb9 Mon Sep 17 00:00:00 2001 From: Yazawazi <47273265+Yazawazi@users.noreply.github.com> Date: Sat, 14 May 2022 00:03:55 +0800 Subject: [PATCH 140/312] feature(serenitea pot): Implementation of the entry function It's being perfected, so don't worry. (probably) --- .../packet/recv/HandlerWidgetDoBagReq.java | 57 +++++++++++++++++++ .../send/PacketWidgetCoolDownNotify.java | 25 ++++++++ .../packet/send/PacketWidgetDoBagRsp.java | 28 +++++++++ .../send/PacketWidgetGadgetDataNotify.java | 26 +++++++++ 4 files changed, 136 insertions(+) create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerWidgetDoBagReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketWidgetCoolDownNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketWidgetDoBagRsp.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketWidgetGadgetDataNotify.java diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerWidgetDoBagReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerWidgetDoBagReq.java new file mode 100644 index 000000000..d9cee08de --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerWidgetDoBagReq.java @@ -0,0 +1,57 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.GadgetData; +import emu.grasscutter.game.entity.EntityVehicle; +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.props.LifeState; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.*; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify; +import emu.grasscutter.server.packet.send.PacketWidgetCoolDownNotify; +import emu.grasscutter.server.packet.send.PacketWidgetDoBagRsp; +import emu.grasscutter.server.packet.send.PacketWidgetGadgetDataNotify; +import emu.grasscutter.utils.Position; + +import java.util.List; + +@Opcodes(PacketOpcodes.WidgetDoBagReq) +public class HandlerWidgetDoBagReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + WidgetDoBagReqOuterClass.WidgetDoBagReq req = WidgetDoBagReqOuterClass.WidgetDoBagReq.parseFrom(payload); + switch (req.getMaterialId()) { + case 220026 -> { + GadgetData gadgetData = GameData.getGadgetDataMap().get(70500025); + Position pos = new Position(req.getWidgetCreatorInfo().getLocationInfo().getPos()); + Position rot = new Position(req.getWidgetCreatorInfo().getLocationInfo().getRot()); + GameEntity entity = new EntityVehicle( + session.getPlayer().getScene(), + session.getPlayer(), + gadgetData.getId(), + 0, + pos, + rot + ); + + session.getPlayer().getScene().addEntity(entity); + + session.send(new PacketWidgetGadgetDataNotify(70500025, List.of(entity.getId()))); // ??? + session.send(new PacketWidgetCoolDownNotify(15, System.currentTimeMillis() + 5000L, true)); + session.send(new PacketWidgetCoolDownNotify(15, System.currentTimeMillis() + 5000L, true)); + // Send twice, and I don't know why, Ask mhy + session.send(new PacketWidgetDoBagRsp()); + } + default -> { + session.send(new PacketWidgetDoBagRsp()); + } + + } + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetCoolDownNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetCoolDownNotify.java new file mode 100644 index 000000000..a73187020 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetCoolDownNotify.java @@ -0,0 +1,25 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WidgetCoolDownDataOuterClass; +import emu.grasscutter.net.proto.WidgetCoolDownNotifyOuterClass; + +public class PacketWidgetCoolDownNotify extends BasePacket { + + public PacketWidgetCoolDownNotify(int id, long coolDownTime, boolean isSuccess) { + super(PacketOpcodes.WidgetCoolDownNotify); + + WidgetCoolDownNotifyOuterClass.WidgetCoolDownNotify proto = WidgetCoolDownNotifyOuterClass.WidgetCoolDownNotify.newBuilder() + .addGroupCoolDownDataList( + WidgetCoolDownDataOuterClass.WidgetCoolDownData.newBuilder() + .setId(id) + .setCoolDownTime(coolDownTime) + .setIsSuccess(isSuccess) + .build() + ) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetDoBagRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetDoBagRsp.java new file mode 100644 index 000000000..7ce5065ea --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetDoBagRsp.java @@ -0,0 +1,28 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WidgetDoBagRspOuterClass; + +public class PacketWidgetDoBagRsp extends BasePacket { + + public PacketWidgetDoBagRsp(int materialId) { + super(PacketOpcodes.WidgetDoBagRsp); + + WidgetDoBagRspOuterClass.WidgetDoBagRsp proto = WidgetDoBagRspOuterClass.WidgetDoBagRsp.newBuilder() + .setMaterialId(materialId) + .setRetcode(0) + .build(); + + this.setData(proto); + } + + public PacketWidgetDoBagRsp() { + super(PacketOpcodes.WidgetDoBagRsp); + + WidgetDoBagRspOuterClass.WidgetDoBagRsp proto = WidgetDoBagRspOuterClass.WidgetDoBagRsp.newBuilder() + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetGadgetDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetGadgetDataNotify.java new file mode 100644 index 000000000..f94c6c10e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetGadgetDataNotify.java @@ -0,0 +1,26 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WidgetGadgetDataNotifyOuterClass; +import emu.grasscutter.net.proto.WidgetGadgetDataOuterClass; + +import java.io.IOException; +import java.util.List; + +public class PacketWidgetGadgetDataNotify extends BasePacket { + public PacketWidgetGadgetDataNotify(int gadgetId, List gadgetEntityIdList) throws IOException { + super(PacketOpcodes.WidgetGadgetDataNotify); + + WidgetGadgetDataNotifyOuterClass.WidgetGadgetDataNotify proto = WidgetGadgetDataNotifyOuterClass.WidgetGadgetDataNotify.newBuilder() + .setWidgetGadgetData( + WidgetGadgetDataOuterClass.WidgetGadgetData.newBuilder() + .setGadgetId(gadgetId) + .addAllGadgetEntityIdList(gadgetEntityIdList) + .build() + ) + .build(); + + this.setData(proto); + } +} From 95a062123f63edfda7a48200e431712e9a1ca74c Mon Sep 17 00:00:00 2001 From: Yazawazi <47273265+Yazawazi@users.noreply.github.com> Date: Sat, 14 May 2022 03:20:20 +0800 Subject: [PATCH 141/312] feature(serenitea pot): Implementation of enter Ugly hard code --- .../emu/grasscutter/game/player/Player.java | 30 ++++++++ .../recv/HandlerHomeChooseModuleReq.java | 26 +++++++ .../packet/recv/HandlerTryEnterHomeReq.java | 68 +++++++++++++++++++ .../send/PacketHomeChooseModuleRsp.java | 19 ++++++ .../send/PacketHomeComfortInfoNotify.java | 40 +++++++++++ .../send/PacketPlayerHomeCompInfoNotify.java | 32 +++++++++ .../packet/send/PacketTryEnterHomeRsp.java | 30 ++++++++ 7 files changed, 245 insertions(+) create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerHomeChooseModuleReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerTryEnterHomeReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketHomeChooseModuleRsp.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketHomeComfortInfoNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketPlayerHomeCompInfoNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketTryEnterHomeRsp.java diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index b7d8470bf..9c9f1ea56 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -87,6 +87,9 @@ public class Player { private Integer widgetId; + private Set realmList; + private Integer currentRealmId; + @Transient private long nextGuid = 0; @Transient private int peerId; @Transient private World world; @@ -313,6 +316,31 @@ public class Player { this.widgetId = widgetId; } + public Set getRealmList() { + return realmList; + } + + public void setRealmList(Set realmList) { + this.realmList = realmList; + } + + public void addRealmList(int realmId) { + if (this.realmList == null) { + this.realmList = new HashSet<>(); + } else if (this.realmList.contains(realmId)) { + return; + } + this.realmList.add(realmId); + } + + public Integer getCurrentRealmId() { + return currentRealmId; + } + + public void setCurrentRealmId(Integer currentRealmId) { + this.currentRealmId = currentRealmId; + } + public Position getPos() { return pos; } @@ -1187,6 +1215,8 @@ public class Player { session.send(new PacketServerCondMeetQuestListUpdateNotify(this)); session.send(new PacketAllWidgetDataNotify(this)); session.send(new PacketWidgetGadgetAllDataNotify()); + session.send(new PacketPlayerHomeCompInfoNotify(this)); + session.send(new PacketHomeComfortInfoNotify(this)); getTodayMoonCard(); // The timer works at 0:0, some users log in after that, use this method to check if they have received a reward today or not. If not, send the reward. diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerHomeChooseModuleReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerHomeChooseModuleReq.java new file mode 100644 index 000000000..5a7c0dbe5 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerHomeChooseModuleReq.java @@ -0,0 +1,26 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.HomeChooseModuleReqOuterClass; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketHomeChooseModuleRsp; +import emu.grasscutter.server.packet.send.PacketHomeComfortInfoNotify; +import emu.grasscutter.server.packet.send.PacketPlayerHomeCompInfoNotify; + + +@Opcodes(PacketOpcodes.HomeChooseModuleReq) +public class HandlerHomeChooseModuleReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + HomeChooseModuleReqOuterClass.HomeChooseModuleReq req = + HomeChooseModuleReqOuterClass.HomeChooseModuleReq.parseFrom(payload); + session.getPlayer().addRealmList(req.getModuleId()); + session.getPlayer().setCurrentRealmId(req.getModuleId()); + session.send(new PacketHomeChooseModuleRsp(req.getModuleId())); + session.send(new PacketPlayerHomeCompInfoNotify(session.getPlayer())); + session.send(new PacketHomeComfortInfoNotify(session.getPlayer())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTryEnterHomeReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTryEnterHomeReq.java new file mode 100644 index 000000000..3e78bcb3a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTryEnterHomeReq.java @@ -0,0 +1,68 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TryEnterHomeReqOuterClass; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketTryEnterHomeRsp; +import emu.grasscutter.utils.Position; + +@Opcodes(PacketOpcodes.TryEnterHomeReq) +public class HandlerTryEnterHomeReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + TryEnterHomeReqOuterClass.TryEnterHomeReq req = + TryEnterHomeReqOuterClass.TryEnterHomeReq.parseFrom(payload); + + if (req.getTargetUid() != session.getPlayer().getUid()) { + // I hope that tomorrow there will be a hero who can support multiplayer mode and write code like a poem + session.send(new PacketTryEnterHomeRsp()); + return; + } + + // Hardcoded for now + switch (session.getPlayer().getCurrentRealmId()) { + case 1: + session.getPlayer().getWorld().transferPlayerToScene( + session.getPlayer(), + 2001, + new Position(839, 319, 137) + ); + break; + + case 2: + session.getPlayer().getWorld().transferPlayerToScene( + session.getPlayer(), + 2002, + new Position(605, 444, 554) + ); + break; + + case 3: + session.getPlayer().getWorld().transferPlayerToScene( + session.getPlayer(), + 2003, + new Position(511, 229, 605) + ); + break; + + case 4: + session.getPlayer().getWorld().transferPlayerToScene( + session.getPlayer(), + 2004, + new Position(239, 187, 536) + ); + break; + + default: + session.send(new PacketTryEnterHomeRsp()); + return; + } + + + session.send(new PacketTryEnterHomeRsp(req.getTargetUid())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketHomeChooseModuleRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketHomeChooseModuleRsp.java new file mode 100644 index 000000000..e7b3ff1ea --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketHomeChooseModuleRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.HomeChooseModuleRspOuterClass; + +public class PacketHomeChooseModuleRsp extends BasePacket { + + public PacketHomeChooseModuleRsp(int moduleId) { + super(PacketOpcodes.HomeChooseModuleRsp); + + HomeChooseModuleRspOuterClass.HomeChooseModuleRsp proto = HomeChooseModuleRspOuterClass.HomeChooseModuleRsp.newBuilder() + .setRetcode(0) + .setModuleId(moduleId) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketHomeComfortInfoNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketHomeComfortInfoNotify.java new file mode 100644 index 000000000..47e46dfdb --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketHomeComfortInfoNotify.java @@ -0,0 +1,40 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.HomeComfortInfoNotifyOuterClass; +import emu.grasscutter.net.proto.HomeModuleComfortInfoOuterClass; + +import java.util.ArrayList; +import java.util.List; + +public class PacketHomeComfortInfoNotify extends BasePacket { + + public PacketHomeComfortInfoNotify(Player player) { + super(PacketOpcodes.HomeComfortInfoNotify); + + if (player.getRealmList() == null) { + // Do not send + return; + } + + List comfortInfoList = new ArrayList<>(); + + for (int moduleId : player.getRealmList()) { + comfortInfoList.add( + HomeModuleComfortInfoOuterClass.HomeModuleComfortInfo.newBuilder() + .setModuleId(moduleId) + .build() + ); + } + + HomeComfortInfoNotifyOuterClass.HomeComfortInfoNotify proto = HomeComfortInfoNotifyOuterClass.HomeComfortInfoNotify + .newBuilder() + .addAllModuleInfoList(comfortInfoList) + .build(); + + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerHomeCompInfoNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerHomeCompInfoNotify.java new file mode 100644 index 000000000..29a6964b5 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerHomeCompInfoNotify.java @@ -0,0 +1,32 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerHomeCompInfoNotifyOuterClass; +import emu.grasscutter.net.proto.PlayerHomeCompInfoOuterClass; + +import java.util.List; + +public class PacketPlayerHomeCompInfoNotify extends BasePacket { + + public PacketPlayerHomeCompInfoNotify(Player player) { + super(PacketOpcodes.PlayerHomeCompInfoNotify); + + if (player.getRealmList() == null) { + // Do not send + return; + } + + PlayerHomeCompInfoNotifyOuterClass.PlayerHomeCompInfoNotify proto = PlayerHomeCompInfoNotifyOuterClass.PlayerHomeCompInfoNotify.newBuilder() + .setCompInfo( + PlayerHomeCompInfoOuterClass.PlayerHomeCompInfo.newBuilder() + .addAllUnlockedModuleIdList(player.getRealmList()) + .addAllLevelupRewardGotLevelList(List.of(1)) // Hardcoded + .build() + ) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTryEnterHomeRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTryEnterHomeRsp.java new file mode 100644 index 000000000..369c44140 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTryEnterHomeRsp.java @@ -0,0 +1,30 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.RetcodeOuterClass; +import emu.grasscutter.net.proto.TryEnterHomeRspOuterClass; + +public class PacketTryEnterHomeRsp extends BasePacket { + + public PacketTryEnterHomeRsp() { + super(PacketOpcodes.TryEnterHomeRsp); + + TryEnterHomeRspOuterClass.TryEnterHomeRsp proto = TryEnterHomeRspOuterClass.TryEnterHomeRsp.newBuilder() + .setRetcode(RetcodeOuterClass.Retcode.RET_SVR_ERROR_VALUE) + .build(); + + this.setData(proto); + } + + public PacketTryEnterHomeRsp(int uid) { + super(PacketOpcodes.TryEnterHomeRsp); + + TryEnterHomeRspOuterClass.TryEnterHomeRsp proto = TryEnterHomeRspOuterClass.TryEnterHomeRsp.newBuilder() + .setRetcode(0) + .setTargetUid(uid) + .build(); + + this.setData(proto); + } +} From ab6aa96144d858a88c8ea35fc13b41c031b024d0 Mon Sep 17 00:00:00 2001 From: Yazawazi <47273265+Yazawazi@users.noreply.github.com> Date: Sat, 14 May 2022 04:50:31 +0800 Subject: [PATCH 142/312] fix(serenitea pot): teleport & read born pos from lua --- .../emu/grasscutter/game/world/World.java | 5 ++ .../packet/recv/HandlerTryEnterHomeReq.java | 48 +++++-------------- 2 files changed, 16 insertions(+), 37 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/world/World.java b/src/main/java/emu/grasscutter/game/world/World.java index 95356a15a..22048077d 100644 --- a/src/main/java/emu/grasscutter/game/world/World.java +++ b/src/main/java/emu/grasscutter/game/world/World.java @@ -268,6 +268,11 @@ public class World implements Iterable { } else if (oldScene == newScene) { enterType = EnterType.ENTER_GOTO; } + + // Home + if (2001 <= newScene.getId() && newScene.getId() <= 2004) { + enterType = EnterType.ENTER_SELF_HOME; + } // Teleport packet player.sendPacket(new PacketPlayerEnterSceneNotify(player, enterType, enterReason, sceneId, pos)); diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTryEnterHomeReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTryEnterHomeReq.java index 3e78bcb3a..5df106df2 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTryEnterHomeReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTryEnterHomeReq.java @@ -1,10 +1,12 @@ package emu.grasscutter.server.packet.recv; -import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.world.Scene; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.TryEnterHomeReqOuterClass; +import emu.grasscutter.scripts.data.SceneConfig; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.PacketTryEnterHomeRsp; import emu.grasscutter.utils.Position; @@ -23,44 +25,16 @@ public class HandlerTryEnterHomeReq extends PacketHandler { return; } - // Hardcoded for now - switch (session.getPlayer().getCurrentRealmId()) { - case 1: - session.getPlayer().getWorld().transferPlayerToScene( - session.getPlayer(), - 2001, - new Position(839, 319, 137) - ); - break; + int realmId = 2000 + session.getPlayer().getCurrentRealmId(); - case 2: - session.getPlayer().getWorld().transferPlayerToScene( - session.getPlayer(), - 2002, - new Position(605, 444, 554) - ); - break; + Scene scene = session.getPlayer().getWorld().getSceneById(realmId); + Position pos = scene.getScriptManager().getConfig().born_pos; - case 3: - session.getPlayer().getWorld().transferPlayerToScene( - session.getPlayer(), - 2003, - new Position(511, 229, 605) - ); - break; - - case 4: - session.getPlayer().getWorld().transferPlayerToScene( - session.getPlayer(), - 2004, - new Position(239, 187, 536) - ); - break; - - default: - session.send(new PacketTryEnterHomeRsp()); - return; - } + session.getPlayer().getWorld().transferPlayerToScene( + session.getPlayer(), + realmId, + pos + ); session.send(new PacketTryEnterHomeRsp(req.getTargetUid())); From 749ef3ff234e05a432357f956fc9f3fcf9b33e60 Mon Sep 17 00:00:00 2001 From: ShigemoriHakura <62388797+ShigemoriHakura@users.noreply.github.com> Date: Sat, 14 May 2022 07:33:07 +0800 Subject: [PATCH 143/312] Add support for codexQuests (#870) --- .../java/emu/grasscutter/data/GameData.java | 7 ++- .../emu/grasscutter/data/def/CodexQuest.java | 42 +++++++++++++++ .../emu/grasscutter/game/player/Player.java | 1 + .../grasscutter/game/quest/GameMainQuest.java | 2 + .../emu/grasscutter/game/quest/GameQuest.java | 1 + .../grasscutter/game/quest/QuestManager.java | 6 +++ .../send/PacketCodexDataFullNotify.java | 54 +++++++++++++++++++ .../send/PacketCodexDataUpdateNotify.java | 27 ++++++++++ 8 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 src/main/java/emu/grasscutter/data/def/CodexQuest.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketCodexDataFullNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketCodexDataUpdateNotify.java diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 75b840202..ed5c469bb 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -9,7 +9,6 @@ import java.util.Map; import emu.grasscutter.Grasscutter; import emu.grasscutter.utils.Utils; import emu.grasscutter.data.custom.AbilityEmbryoEntry; -import emu.grasscutter.data.custom.AbilityModifier; import emu.grasscutter.data.custom.AbilityModifierEntry; import emu.grasscutter.data.custom.OpenConfigEntry; import emu.grasscutter.data.custom.MainQuestData; @@ -65,6 +64,8 @@ public class GameData { private static final Int2ObjectMap sceneDataMap = new Int2ObjectLinkedOpenHashMap<>(); private static final Int2ObjectMap fetterDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap codexQuestMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap codexQuestIdMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap rewardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap worldLevelDataMap = new Int2ObjectOpenHashMap<>(); @@ -293,6 +294,10 @@ public class GameData { return fetters; } + public static Int2ObjectMap getCodexQuestMap(){return codexQuestMap;} + + public static Int2ObjectMap getCodexQuestIdMap(){return codexQuestIdMap;} + public static Int2ObjectMap getWorldLevelDataMap() { return worldLevelDataMap; } diff --git a/src/main/java/emu/grasscutter/data/def/CodexQuest.java b/src/main/java/emu/grasscutter/data/def/CodexQuest.java new file mode 100644 index 000000000..578837e04 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/CodexQuest.java @@ -0,0 +1,42 @@ +package emu.grasscutter.data.def; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; + +@ResourceType(name = {"QuestCodexExcelConfigData.json"}, loadPriority = ResourceType.LoadPriority.HIGH) +public class CodexQuest extends GameResource { + private int Id; + private int ParentQuestId; + private int ChapterId; + private int SortOrder; + private boolean IsDisuse; + + public int getParentQuestId() { + return ParentQuestId; + } + + public int getId() { + return Id; + } + + public int getChapterId() { + return ChapterId; + } + + public int getSortOrder() { + return SortOrder; + } + + public boolean getIsDisuse() { + return IsDisuse; + } + + @Override + public void onLoad() { + if(!this.getIsDisuse()) { + GameData.getCodexQuestIdMap().put(this.getParentQuestId(), this); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 9c9f1ea56..f86e09370 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -1212,6 +1212,7 @@ public class Player { session.send(new PacketAvatarDataNotify(this)); session.send(new PacketFinishedParentQuestNotify(this)); session.send(new PacketQuestListNotify(this)); + session.send(new PacketCodexDataFullNotify(this)); session.send(new PacketServerCondMeetQuestListUpdateNotify(this)); session.send(new PacketAllWidgetDataNotify(this)); session.send(new PacketWidgetGadgetAllDataNotify()); diff --git a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java index 613819d0b..c298913cc 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java @@ -3,6 +3,7 @@ package emu.grasscutter.game.quest; import java.util.HashMap; import java.util.Map; +import emu.grasscutter.server.packet.send.PacketCodexDataUpdateNotify; import org.bson.types.ObjectId; import dev.morphia.annotations.Entity; @@ -91,6 +92,7 @@ public class GameMainQuest { this.isFinished = true; this.state = ParentQuestState.PARENT_QUEST_STATE_FINISHED; this.getOwner().getSession().send(new PacketFinishedParentQuestUpdateNotify(this)); + this.getOwner().getSession().send(new PacketCodexDataUpdateNotify(this)); this.save(); } diff --git a/src/main/java/emu/grasscutter/game/quest/GameQuest.java b/src/main/java/emu/grasscutter/game/quest/GameQuest.java index 5e1126fcb..3caf950ba 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameQuest.java @@ -11,6 +11,7 @@ import emu.grasscutter.game.player.Player; import emu.grasscutter.game.quest.enums.LogicType; import emu.grasscutter.game.quest.enums.QuestState; import emu.grasscutter.net.proto.QuestOuterClass.Quest; +import emu.grasscutter.server.packet.send.PacketCodexDataUpdateNotify; import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify; import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify; import emu.grasscutter.utils.Utils; diff --git a/src/main/java/emu/grasscutter/game/quest/QuestManager.java b/src/main/java/emu/grasscutter/game/quest/QuestManager.java index 745ce9ef8..0d81834f0 100644 --- a/src/main/java/emu/grasscutter/game/quest/QuestManager.java +++ b/src/main/java/emu/grasscutter/game/quest/QuestManager.java @@ -66,6 +66,12 @@ public class QuestManager { } } } + + public void forEachMainQuest(Consumer callback) { + for (GameMainQuest mainQuest : getQuests().values()) { + callback.accept(mainQuest); + } + } // TODO public void forEachActiveQuest(Consumer callback) { diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketCodexDataFullNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketCodexDataFullNotify.java new file mode 100644 index 000000000..760c3b3d2 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketCodexDataFullNotify.java @@ -0,0 +1,54 @@ +package emu.grasscutter.server.packet.send; + +import java.util.Collections; +import java.util.List; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.CodexDataFullNotifyOuterClass.CodexDataFullNotify; +import emu.grasscutter.net.proto.CodexTypeDataOuterClass.CodexTypeData; +import emu.grasscutter.net.proto.CodexTypeOuterClass; +import emu.grasscutter.server.game.GameSession; + +public class PacketCodexDataFullNotify extends BasePacket { + public PacketCodexDataFullNotify(Player player) { + super(PacketOpcodes.CodexDataFullNotify, true); + + //Quests + CodexTypeData.Builder questTypeData = CodexTypeData.newBuilder() + .setTypeValue(1); + + //Tips + CodexTypeData.Builder pushTipsTypeData = CodexTypeData.newBuilder() + .setTypeValue(6); + + //Views + CodexTypeData.Builder viewTypeData = CodexTypeData.newBuilder() + .setTypeValue(7); + + //Weapons + CodexTypeData.Builder weaponTypeData = CodexTypeData.newBuilder() + .setTypeValue(2); + + + player.getQuestManager().forEachMainQuest(mainQuest -> { + if(mainQuest.isFinished()){ + var codexQuest = GameData.getCodexQuestIdMap().get(mainQuest.getParentQuestId()); + if(codexQuest != null){ + questTypeData.addCodexIdList(codexQuest.getId()).addAllHaveViewedList(Collections.singleton(true)); + } + } + }); + + CodexDataFullNotify.Builder proto = CodexDataFullNotify.newBuilder() + .addTypeDataList(questTypeData.build()) + .addTypeDataList(pushTipsTypeData.build()) + .addTypeDataList(viewTypeData.build()) + .addTypeDataList(weaponTypeData); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketCodexDataUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketCodexDataUpdateNotify.java new file mode 100644 index 000000000..c7318bd91 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketCodexDataUpdateNotify.java @@ -0,0 +1,27 @@ +package emu.grasscutter.server.packet.send; + +import java.util.Collections; +import java.util.List; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.CodexDataUpdateNotifyOuterClass.CodexDataUpdateNotify; +import emu.grasscutter.server.game.GameSession; + +public class PacketCodexDataUpdateNotify extends BasePacket { + public PacketCodexDataUpdateNotify(GameMainQuest quest) { + super(PacketOpcodes.CodexDataUpdateNotify, true); + var codexQuest = GameData.getCodexQuestIdMap().get(quest.getParentQuestId()); + if(codexQuest != null){ + CodexDataUpdateNotify proto = CodexDataUpdateNotify.newBuilder() + .setTypeValue(1) + .setId(codexQuest.getId()) + .build(); + this.setData(proto); + } + } +} From 82698d56238e84fd5cb5f45ee3181ba639b820b7 Mon Sep 17 00:00:00 2001 From: tester233 <105267106+tester233@users.noreply.github.com> Date: Fri, 13 May 2022 23:36:30 +0800 Subject: [PATCH 144/312] Improve text --- src/main/resources/languages/zh-CN.json | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 4ece73fd9..aaeac5a86 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -17,7 +17,7 @@ "default_password": "[Dispatch] 成功加载 keystore 默认密码。请考虑将 config.json 的默认密码设置为 123456" }, "authentication": { - "default_unable_to_verify": "[Authentication] 称为 verifyUser 的 method 在默认验证程序中不可用" + "default_unable_to_verify": "[Authentication] 称为 verifyUser 的方法在默认验证程序中不可用" }, "no_commands_error": "此命令不适用于 Dispatch-only 模式", "unhandled_request_error": "[Dispatch] 潜在的未处理请求:%s %s", @@ -215,6 +215,14 @@ "success": "坐标:%s, %s, %s\n场景ID:%s", "description": "获取所在位置" }, + "quest": { + "description": "添加或完成任务", + "usage": "quest [任务ID]", + "added": "已添加任务 %s", + "finished": "已完成任务 %s", + "not_found": "未找到任务", + "invalid_id": "无效的任务ID" + }, "reload": { "reload_start": "正在重载配置文件和数据。", "reload_done": "重载完成。", @@ -356,9 +364,9 @@ "gacha": { "details": { "title": "祈愿详情", - "available_five_stars": "出现的五星物品", - "available_four_stars": "出现的四星物品", - "available_three_stars": "出现的三星物品", + "available_five_stars": "可获得的5星物品", + "available_four_stars": "可获得的4星物品", + "available_three_stars": "可获得的3星物品", "template_missing": "缺失文件:data/gacha_details.html" } } From 248b655f07c96d322fdb865acffa3ddf82da000d Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 13 May 2022 16:35:03 -0700 Subject: [PATCH 145/312] Use scene types instead of hardcoding scene ids for checking enter reason --- src/main/java/emu/grasscutter/game/world/World.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/world/World.java b/src/main/java/emu/grasscutter/game/world/World.java index 22048077d..ccbe4b841 100644 --- a/src/main/java/emu/grasscutter/game/world/World.java +++ b/src/main/java/emu/grasscutter/game/world/World.java @@ -10,6 +10,7 @@ import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player.SceneLoadState; import emu.grasscutter.game.props.EnterReason; import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.props.SceneType; import emu.grasscutter.data.GameData; import emu.grasscutter.data.def.DungeonData; import emu.grasscutter.data.def.SceneData; @@ -267,11 +268,9 @@ public class World implements Iterable { enterReason = EnterReason.DungeonEnter; } else if (oldScene == newScene) { enterType = EnterType.ENTER_GOTO; - } - - // Home - if (2001 <= newScene.getId() && newScene.getId() <= 2004) { - enterType = EnterType.ENTER_SELF_HOME; + } else if (newScene.getSceneType() == SceneType.SCENE_HOME_WORLD) { + // Home + enterType = EnterType.ENTER_SELF_HOME; } // Teleport packet From 20e3b8ffda1e695ba479da243e1892ea359d1bf3 Mon Sep 17 00:00:00 2001 From: ShiroSaki <62388797+ShigemoriHakura@users.noreply.github.com> Date: Sat, 14 May 2022 04:31:27 +0800 Subject: [PATCH 146/312] add support for announcement page --- .gitignore | 1 + data/GameAnnouncementList.json | 10 ++- .../server/dispatch/DispatchServer.java | 6 ++ .../http/AnnouncementIndexHandler.java | 61 +++++++++++++++++++ 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 src/main/java/emu/grasscutter/server/dispatch/http/AnnouncementIndexHandler.java diff --git a/.gitignore b/.gitignore index 6fd78ed3b..9a298a89a 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,4 @@ BuildConfig.java # macOS .DS_Store +data/hk4e/announcement/ diff --git a/data/GameAnnouncementList.json b/data/GameAnnouncementList.json index f6566960a..7464b3b0f 100644 --- a/data/GameAnnouncementList.json +++ b/data/GameAnnouncementList.json @@ -57,5 +57,13 @@ "mi18n_name": "Activity" } ], - "timezone": -5 + "timezone": -5, + "alert": false, + "alert_id": 0, + "pic_list": [], + "pic_total": 0, + "pic_type_list": [], + "pic_alert": false, + "pic_alert_id": 0, + "static_sign": "" } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index 7e439a1a4..7153542a4 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -16,6 +16,7 @@ import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo; import emu.grasscutter.server.dispatch.authentication.AuthenticationHandler; import emu.grasscutter.server.dispatch.authentication.DefaultAuthenticationHandler; +import emu.grasscutter.server.dispatch.http.AnnouncementIndexHandler; import emu.grasscutter.server.dispatch.http.GachaDetailsHandler; import emu.grasscutter.server.dispatch.http.GachaRecordHandler; import emu.grasscutter.server.dispatch.json.*; @@ -443,6 +444,11 @@ public final class DispatchServer { // gacha details httpServer.get("/gacha/details", new GachaDetailsHandler()); + // announcement index + httpServer.get("/hk4e/announcement/*", new AnnouncementIndexHandler()); + httpServer.get("/sw.js", new AnnouncementIndexHandler()); + httpServer.get("/dora/lib/vue/2.6.11/vue.min.js", new AnnouncementIndexHandler()); + // static file support for plugins httpServer.raw().config.precompressStaticFiles = false; // If this isn't set to false, files such as images may appear corrupted when serving static files diff --git a/src/main/java/emu/grasscutter/server/dispatch/http/AnnouncementIndexHandler.java b/src/main/java/emu/grasscutter/server/dispatch/http/AnnouncementIndexHandler.java new file mode 100644 index 000000000..7e55eac7e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/dispatch/http/AnnouncementIndexHandler.java @@ -0,0 +1,61 @@ +package emu.grasscutter.server.dispatch.http; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.Account; +import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.Utils; +import express.http.HttpContextHandler; +import express.http.Request; +import express.http.Response; + +import java.io.File; +import java.io.IOException; +import java.util.Objects; + +import static emu.grasscutter.Configuration.DATA; + +public class AnnouncementIndexHandler implements HttpContextHandler { + private final String render_template; + private final String render_swjs; + private final String render_vueminjs; + + public AnnouncementIndexHandler() { + File template = new File(Utils.toFilePath(DATA("/hk4e/announcement/index.html"))); + File swjs = new File(Utils.toFilePath(DATA("/hk4e/announcement/sw.js"))); + File vueminjs = new File(Utils.toFilePath(DATA("/hk4e/announcement/vue.min.js"))); + this.render_template = template.exists() ? new String(FileUtils.read(template)) : null; + this.render_swjs = swjs.exists() ? new String(FileUtils.read(swjs)) : null; + this.render_vueminjs = vueminjs.exists() ? new String(FileUtils.read(vueminjs)) : null; + } + + @Override + public void handle(Request req, Response res) throws IOException { + if (Objects.equals(req.path(), "/sw.js")) { + res.send(render_swjs); + }else if(Objects.equals(req.path(), "/hk4e/announcement/index.html")) { + res.send(render_template); + }else if(Objects.equals(req.path(), "/dora/lib/vue/2.6.11/vue.min.js")){ + res.send(render_vueminjs); + }else{ + File renderFile = new File(Utils.toFilePath(DATA(req.path()))); + if(renderFile.exists()){ + String ext = req.path().substring(req.path().lastIndexOf(".") + 1); + switch(ext){ + case "css": + res.type("text/css"); + res.send(FileUtils.read(renderFile)); + break; + case "js": + default: + res.send(FileUtils.read(renderFile)); + break; + } + }else{ + Grasscutter.getLogger().info( "File not exist: " + req.path()); + } + } + + + } +} From 3adf0d448c3a55a7ec36de532461bfe6af4d3557 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Fri, 13 May 2022 23:22:30 -0400 Subject: [PATCH 147/312] Refactor dispatch (now called HTTP) server (pt. 2) --- .../java/emu/grasscutter/Grasscutter.java | 18 +- .../command/commands/ReloadCommand.java | 2 +- .../grasscutter/game/gacha/GachaBanner.java | 6 +- .../grasscutter/plugin/api/ServerHook.java | 43 +- .../scripts/serializer/LuaSerializer.java | 6 +- .../server/dispatch/AnnouncementHandler.java | 38 -- .../server/dispatch/ClientLogHandler.java | 19 - .../server/dispatch/DispatchServer.java | 532 ------------------ .../authentication/AuthenticationHandler.java | 16 - .../DefaultAuthenticationHandler.java | 80 --- .../dispatch/http/GachaRecordHandler.java | 54 -- .../dispatch/json/ComboTokenReqJson.java | 15 - .../dispatch/json/ComboTokenResJson.java | 17 - .../json/LoginAccountRequestJson.java | 7 - .../server/dispatch/json/LoginResultJson.java | 38 -- .../dispatch/json/LoginTokenRequestJson.java | 6 - .../grasscutter/server/http/HttpServer.java | 4 +- .../server/http/dispatch/RegionHandler.java | 14 +- .../http/handlers/AnnouncementsHandler.java | 8 +- .../server/http/handlers/GachaHandler.java | 71 +++ .../server/http/handlers/GenericHandler.java | 44 +- .../http/handlers/LegacyAuthHandler.java | 17 + .../server/http/handlers/LogHandler.java | 12 +- .../objects/HttpJsonResponse.java} | 6 +- .../packet/send/PacketPlayerLoginRsp.java | 4 +- 25 files changed, 195 insertions(+), 882 deletions(-) delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/AnnouncementHandler.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/ClientLogHandler.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenReqJson.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenResJson.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/json/LoginAccountRequestJson.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/json/LoginResultJson.java delete mode 100644 src/main/java/emu/grasscutter/server/dispatch/json/LoginTokenRequestJson.java create mode 100644 src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java create mode 100644 src/main/java/emu/grasscutter/server/http/handlers/LegacyAuthHandler.java rename src/main/java/emu/grasscutter/server/{dispatch/DispatchHttpJsonHandler.java => http/objects/HttpJsonResponse.java} (90%) diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index bddfa9964..30768bcb5 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -11,9 +11,7 @@ import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.server.http.HttpServer; import emu.grasscutter.server.http.dispatch.DispatchHandler; -import emu.grasscutter.server.http.handlers.AnnouncementsHandler; -import emu.grasscutter.server.http.handlers.GenericHandler; -import emu.grasscutter.server.http.handlers.LogHandler; +import emu.grasscutter.server.http.handlers.*; import emu.grasscutter.server.http.dispatch.RegionHandler; import emu.grasscutter.utils.ConfigContainer; import emu.grasscutter.utils.Utils; @@ -33,7 +31,6 @@ import ch.qos.logback.classic.Logger; import emu.grasscutter.data.ResourceLoader; import emu.grasscutter.database.DatabaseManager; import emu.grasscutter.utils.Language; -import emu.grasscutter.server.dispatch.DispatchServer; import emu.grasscutter.server.game.GameServer; import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.Crypto; @@ -54,7 +51,6 @@ public final class Grasscutter { private static int day; // Current day of week. - private static DispatchServer dispatchServer; private static HttpServer httpServer; private static GameServer gameServer; private static PluginManager pluginManager; @@ -113,11 +109,10 @@ public final class Grasscutter { authenticationSystem = new DefaultAuthentication(); // Create server instances. - dispatchServer = new DispatchServer(); httpServer = new HttpServer(); gameServer = new GameServer(); // Create a server hook instance with both servers. - new ServerHook(gameServer, dispatchServer); + new ServerHook(gameServer, httpServer); // Create plugin manager instance. pluginManager = new PluginManager(); @@ -129,14 +124,15 @@ public final class Grasscutter { httpServer.addRouter(GenericHandler.class); httpServer.addRouter(AnnouncementsHandler.class); httpServer.addRouter(DispatchHandler.class); + httpServer.addRouter(LegacyAuthHandler.class); + httpServer.addRouter(GachaHandler.class); // Start servers. var runMode = SERVER.runMode; if (runMode == ServerRunMode.HYBRID) { - dispatchServer.start(); + httpServer.start(); gameServer.start(); } else if (runMode == ServerRunMode.DISPATCH_ONLY) { - dispatchServer.start(); httpServer.start(); } else if (runMode == ServerRunMode.GAME_ONLY) { gameServer.start(); @@ -258,8 +254,8 @@ public final class Grasscutter { return gson; } - public static DispatchServer getDispatchServer() { - return dispatchServer; + public static HttpServer getHttpServer() { + return httpServer; } public static GameServer getGameServer() { diff --git a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java index 9414a89c4..984fd7d60 100644 --- a/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/ReloadCommand.java @@ -21,7 +21,7 @@ public final class ReloadCommand implements CommandHandler { Grasscutter.getGameServer().getGachaManager().load(); Grasscutter.getGameServer().getDropManager().load(); Grasscutter.getGameServer().getShopManager().load(); - Grasscutter.getDispatchServer().loadQueries(); + // Grasscutter.getHttpServer().loadQueries(); // Is this practical? CommandHandler.sendMessage(sender, translate(sender, "commands.reload.reload_done")); } diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java index dce433fcf..7602f7c06 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -98,9 +98,9 @@ public class GachaBanner { } public GachaInfo toProto(String sessionKey) { - String record = "http" + (DISPATCH_INFO.encryption.useInRouting ? "s" : "") + "://" - + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" - + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + String record = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://" + + lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":" + + lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort) + "/gacha?s=" + sessionKey + "&gachaType=" + gachaType; // Grasscutter.getLogger().info("record = " + record); GachaInfo.Builder info = GachaInfo.newBuilder() diff --git a/src/main/java/emu/grasscutter/plugin/api/ServerHook.java b/src/main/java/emu/grasscutter/plugin/api/ServerHook.java index a37abfb62..ffa19110d 100644 --- a/src/main/java/emu/grasscutter/plugin/api/ServerHook.java +++ b/src/main/java/emu/grasscutter/plugin/api/ServerHook.java @@ -1,10 +1,13 @@ package emu.grasscutter.plugin.api; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.auth.AuthenticationSystem; import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.game.player.Player; -import emu.grasscutter.server.dispatch.DispatchServer; import emu.grasscutter.server.game.GameServer; +import emu.grasscutter.server.http.HttpServer; +import emu.grasscutter.server.http.Router; import java.util.LinkedList; import java.util.List; @@ -15,7 +18,7 @@ import java.util.List; public final class ServerHook { private static ServerHook instance; private final GameServer gameServer; - private final DispatchServer dispatchServer; + private final HttpServer httpServer; /** * Gets the server hook instance. @@ -28,11 +31,11 @@ public final class ServerHook { /** * Hooks into a server. * @param gameServer The game server to hook into. - * @param dispatchServer The dispatch server to hook into. + * @param httpServer The HTTP server to hook into. */ - public ServerHook(GameServer gameServer, DispatchServer dispatchServer) { + public ServerHook(GameServer gameServer, HttpServer httpServer) { this.gameServer = gameServer; - this.dispatchServer = dispatchServer; + this.httpServer = httpServer; instance = this; } @@ -45,10 +48,10 @@ public final class ServerHook { } /** - * @return The dispatch server. + * @return The HTTP server. */ - public DispatchServer getDispatchServer() { - return this.dispatchServer; + public HttpServer getHttpServer() { + return this.httpServer; } /** @@ -70,4 +73,28 @@ public final class ServerHook { Command commandData = clazz.getAnnotation(Command.class); this.gameServer.getCommandMap().registerCommand(commandData.label(), handler); } + + /** + * Adds a router using an instance of a class. + * @param router A router instance. + */ + public void addRouter(Router router) { + this.addRouter(router.getClass()); + } + + /** + * Adds a router using a class. + * @param router The class of the router. + */ + public void addRouter(Class router) { + this.httpServer.addRouter(router); + } + + /** + * Sets the server's authentication system. + * @param authSystem An instance of the authentication system. + */ + public void setAuthSystem(AuthenticationSystem authSystem) { + Grasscutter.setAuthenticationSystem(authSystem); + } } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java index a63328b55..8924a33ae 100644 --- a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java +++ b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java @@ -70,16 +70,14 @@ public class LuaSerializer implements Serializer { } try { + //noinspection ConfusingArgumentToVarargsMethod object = type.getDeclaredConstructor().newInstance(null); LuaValue[] keys = table.keys(); for (LuaValue k : keys) { try { Field field = object.getClass().getDeclaredField(k.checkjstring()); - if (field == null) { - continue; - } - + field.setAccessible(true); LuaValue keyValue = table.get(k); diff --git a/src/main/java/emu/grasscutter/server/dispatch/AnnouncementHandler.java b/src/main/java/emu/grasscutter/server/dispatch/AnnouncementHandler.java deleted file mode 100644 index ab752c8e1..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/AnnouncementHandler.java +++ /dev/null @@ -1,38 +0,0 @@ -package emu.grasscutter.server.dispatch; - -import emu.grasscutter.Grasscutter; -import express.http.HttpContextHandler; -import express.http.Request; -import express.http.Response; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.Objects; - -import static emu.grasscutter.Configuration.*; - -public final class AnnouncementHandler implements HttpContextHandler { - @Override - public void handle(Request request, Response response) throws IOException {//event - if (Objects.equals(request.baseUrl(), "/common/hk4e_global/announcement/api/getAnnContent")) { - response.send("{\"retcode\":0,\"message\":\"OK\",\"data\":" + readToString(new File(DATA("GameAnnouncement.json"))) +"}"); - } else if (Objects.equals(request.baseUrl(), "/common/hk4e_global/announcement/api/getAnnList")) { - String data = readToString(new File(DATA("GameAnnouncementList.json"))).replace("System.currentTimeMillis()",String.valueOf(System.currentTimeMillis())); - response.send("{\"retcode\":0,\"message\":\"OK\",\"data\": "+data +"}"); - } - } - @SuppressWarnings("ResultOfMethodCallIgnored") - private static String readToString(File file) { - long length = file.length(); - byte[] content = new byte[(int) length]; - try { - FileInputStream in = new FileInputStream(file); - in.read(content); in.close(); - } catch (IOException ignored) { - Grasscutter.getLogger().warn("File not found: " + file.getAbsolutePath()); - } - - return new String(content); - } -} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/server/dispatch/ClientLogHandler.java b/src/main/java/emu/grasscutter/server/dispatch/ClientLogHandler.java deleted file mode 100644 index b3d48dbbb..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/ClientLogHandler.java +++ /dev/null @@ -1,19 +0,0 @@ -package emu.grasscutter.server.dispatch; - -import express.http.HttpContextHandler; -import express.http.Request; -import express.http.Response; - -import java.io.IOException; - -/** - * Used for processing crash dumps and logs generated by the game. - * Logs are in JSON, and are sent to the server for logging. - */ -public final class ClientLogHandler implements HttpContextHandler { - @Override - public void handle(Request request, Response response) throws IOException { - // TODO: Figure out how to dump request body and log to file. - response.send("{\"code\":0}"); - } -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java deleted file mode 100644 index 8b8a9a185..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ /dev/null @@ -1,532 +0,0 @@ -package emu.grasscutter.server.dispatch; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.protobuf.ByteString; - -import emu.grasscutter.GameConstants; -import emu.grasscutter.Grasscutter; -import emu.grasscutter.Grasscutter.ServerDebugMode; -import emu.grasscutter.Grasscutter.ServerRunMode; -import emu.grasscutter.database.DatabaseHelper; -import emu.grasscutter.game.Account; -import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass.QueryCurrRegionHttpRsp; -import emu.grasscutter.net.proto.QueryRegionListHttpRspOuterClass.QueryRegionListHttpRsp; -import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; -import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo; -import emu.grasscutter.server.dispatch.authentication.AuthenticationHandler; -import emu.grasscutter.server.dispatch.authentication.DefaultAuthenticationHandler; -import emu.grasscutter.server.dispatch.http.GachaRecordHandler; -import emu.grasscutter.server.dispatch.json.*; -import emu.grasscutter.server.dispatch.json.ComboTokenReqJson.LoginTokenData; -import emu.grasscutter.server.event.dispatch.QueryAllRegionsEvent; -import emu.grasscutter.server.event.dispatch.QueryCurrentRegionEvent; -import emu.grasscutter.tools.Tools; -import emu.grasscutter.utils.FileUtils; -import emu.grasscutter.utils.Utils; -import express.Express; -import io.javalin.http.staticfiles.Location; -import org.eclipse.jetty.server.Connector; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; -import org.eclipse.jetty.util.ssl.SslContextFactory; - -import java.io.*; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.util.*; - -import static emu.grasscutter.utils.Language.translate; -import static emu.grasscutter.Configuration.*; - -public final class DispatchServer { - public static String query_region_list = ""; - public static String query_cur_region = ""; - - private final Gson gson; - private final String defaultServerName = "os_usa"; - - public String regionListBase64; - public Map regions; - private AuthenticationHandler authHandler; - private Express httpServer; - - public DispatchServer() { - this.regions = new HashMap<>(); - this.gson = new GsonBuilder().create(); - - this.loadQueries(); - this.initRegion(); - } - - public Express getServer() { - return httpServer; - } - - public void setHttpServer(Express httpServer) { - this.httpServer.stop(); - this.httpServer = httpServer; - this.httpServer.listen(DISPATCH_INFO.bindPort); - } - - public Gson getGsonFactory() { - return gson; - } - - public QueryCurrRegionHttpRsp getCurrRegion() { - // Needs to be fixed by having the game servers connect to the dispatch server. - if (SERVER.runMode == ServerRunMode.HYBRID) { - return regions.get(defaultServerName).parsedRegionQuery; - } - - Grasscutter.getLogger().warn("[Dispatch] Unsupported run mode for getCurrRegion()"); - return null; - } - - public void loadQueries() { - File file; - - file = new File(DATA("query_region_list.txt")); - if (file.exists()) { - query_region_list = new String(FileUtils.read(file)); - } else { - Grasscutter.getLogger().warn("[Dispatch] query_region_list not found! Using default region list."); - } - - file = new File(DATA("query_cur_region.txt")); - if (file.exists()) { - query_cur_region = new String(FileUtils.read(file)); - } else { - Grasscutter.getLogger().warn("[Dispatch] query_cur_region not found! Using default current region."); - } - } - - private void initRegion() { - try { - byte[] decoded = Base64.getDecoder().decode(query_region_list); - QueryRegionListHttpRsp rl = QueryRegionListHttpRsp.parseFrom(decoded); - - byte[] decoded2 = Base64.getDecoder().decode(query_cur_region); - QueryCurrRegionHttpRsp regionQuery = QueryCurrRegionHttpRsp.parseFrom(decoded2); - - List servers = new ArrayList<>(); - List usedNames = new ArrayList<>(); // List to check for potential naming conflicts. - if (SERVER.runMode == ServerRunMode.HYBRID) { // Automatically add the game server if in hybrid mode. - RegionSimpleInfo server = RegionSimpleInfo.newBuilder() - .setName("os_usa") - .setTitle(DISPATCH_INFO.defaultName) - .setType("DEV_PUBLIC") - .setDispatchUrl( - "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://" - + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" - + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) - + "/query_cur_region/" + defaultServerName) - .build(); - usedNames.add(defaultServerName); - servers.add(server); - - RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder() - .setGateserverIp(lr(GAME_INFO.accessAddress, GAME_INFO.bindAddress)) - .setGateserverPort(lr(GAME_INFO.accessPort, GAME_INFO.bindPort)) - .setSecretKey(ByteString.copyFrom(FileUtils.read(KEYS_FOLDER + "/dispatchSeed.bin"))) - .build(); - - QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(serverRegion).build(); - regions.put(defaultServerName, new RegionData(parsedRegionQuery, - Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray()))); - - } else if (DISPATCH_INFO.regions.length == 0) { - Grasscutter.getLogger().error("[Dispatch] There are no game servers available. Exiting due to unplayable state."); - System.exit(1); - } - - for (var regionInfo : DISPATCH_INFO.regions) { - if (usedNames.contains(regionInfo.Name)) { - Grasscutter.getLogger().error("Region name already in use."); - continue; - } - RegionSimpleInfo server = RegionSimpleInfo.newBuilder() - .setName(regionInfo.Name) - .setTitle(regionInfo.Title) - .setType("DEV_PUBLIC") - .setDispatchUrl( - "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://" - + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" - + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) - + "/query_cur_region/" + regionInfo.Name) - .build(); - usedNames.add(regionInfo.Name); - servers.add(server); - - RegionInfo serverRegion = regionQuery.getRegionInfo().toBuilder() - .setGateserverIp(regionInfo.Ip) - .setGateserverPort(regionInfo.Port) - .setSecretKey(ByteString - .copyFrom(FileUtils.read(KEYS_FOLDER + "/dispatchSeed.bin"))) - .build(); - - QueryCurrRegionHttpRsp parsedRegionQuery = regionQuery.toBuilder().setRegionInfo(serverRegion).build(); - regions.put(regionInfo.Name, new RegionData(parsedRegionQuery, - Base64.getEncoder().encodeToString(parsedRegionQuery.toByteString().toByteArray()))); - } - - QueryRegionListHttpRsp regionList = QueryRegionListHttpRsp.newBuilder() - .addAllRegionList(servers) - .setClientSecretKey(rl.getClientSecretKey()) - .setClientCustomConfigEncrypted(rl.getClientCustomConfigEncrypted()) - .setEnableLoginPc(true) - .build(); - - this.regionListBase64 = Base64.getEncoder().encodeToString(regionList.toByteString().toByteArray()); - } catch (Exception exception) { - Grasscutter.getLogger().error("[Dispatch] Error while initializing region info!", exception); - } - } - - public void start() throws Exception { - httpServer = new Express(config -> { - config.server(() -> { - Server server = new Server(); - ServerConnector serverConnector; - - if(HTTP_ENCRYPTION.useEncryption) { - SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); - File keystoreFile = new File(HTTP_ENCRYPTION.keystore); - - if(keystoreFile.exists()) { - try { - sslContextFactory.setKeyStorePath(keystoreFile.getPath()); - sslContextFactory.setKeyStorePassword(HTTP_ENCRYPTION.keystorePassword); - } catch (Exception e) { - e.printStackTrace(); - Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.password_error")); - - try { - sslContextFactory.setKeyStorePath(keystoreFile.getPath()); - sslContextFactory.setKeyStorePassword("123456"); - Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.default_password")); - } catch (Exception e2) { - Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.general_error")); - e2.printStackTrace(); - } - } - - serverConnector = new ServerConnector(server, sslContextFactory); - } else { - Grasscutter.getLogger().warn(translate("messages.dispatch.keystore.no_keystore_error")); - HTTP_ENCRYPTION.useEncryption = false; - - serverConnector = new ServerConnector(server); - } - } else { - serverConnector = new ServerConnector(server); - } - - serverConnector.setPort(DISPATCH_INFO.bindPort); - server.setConnectors(new Connector[]{serverConnector}); - return server; - }); - - config.enforceSsl = HTTP_ENCRYPTION.useEncryption; - if(SERVER.debugLevel == ServerDebugMode.ALL) { - config.enableDevLogging(); - } - - if (HTTP_POLICIES.cors.enabled) { - var corsPolicy = HTTP_POLICIES.cors; - if (corsPolicy.allowedOrigins.length > 0) - config.enableCorsForOrigin(corsPolicy.allowedOrigins); - else config.enableCorsForAllOrigins(); - } - }); - - httpServer.get("/", (req, res) -> res.send("" + translate("messages.status.welcome") + "")); - - httpServer.raw().error(404, ctx -> { - if(SERVER.debugLevel == ServerDebugMode.MISSING) { - Grasscutter.getLogger().info(translate("messages.dispatch.unhandled_request_error", ctx.method(), ctx.url())); - } - ctx.contentType("text/html"); - ctx.result(""); // I'm like 70% sure this won't break anything. - }); - - // Authentication Handler - // These routes are so that authentication routes are always the same no matter what auth system is used. - httpServer.get("/authentication/type", (req, res) -> { - res.send(this.getAuthHandler().getClass().getName()); - }); - - httpServer.post("/authentication/login", (req, res) -> this.getAuthHandler().handleLogin(req, res)); - httpServer.post("/authentication/register", (req, res) -> this.getAuthHandler().handleRegister(req, res)); - httpServer.post("/authentication/change_password", (req, res) -> this.getAuthHandler().handleChangePassword(req, res)); - - // Server Status - httpServer.get("/status/server", (req, res) -> { - - int playerCount = Grasscutter.getGameServer().getPlayers().size(); - String version = GameConstants.VERSION; - - res.send("{\"retcode\":0,\"status\":{\"playerCount\":" + playerCount + ",\"version\":\"" + version + "\"}}"); - }); - - // Dispatch - httpServer.get("/query_region_list", (req, res) -> { - // Log - Grasscutter.getLogger().info(String.format("[Dispatch] Client %s request: query_region_list", req.ip())); - - // Invoke event. - QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListBase64); event.call(); - // Respond with event result. - res.send(event.getRegionList()); - }); - - // /server/:id -> 2.6.5x - httpServer.get("/query_cur_region/:id", (req, res) -> { - String regionName = req.params("id"); - // Log - Grasscutter.getLogger().info( - String.format("Client %s request: query_cur_region/%s", req.ip(), regionName)); - // Create a response form the request query parameters - String response = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="; - if (req.query().values().size() > 0) { - response = regions.get(regionName).Base64; - } - - // Invoke event. - QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(response); event.call(); - // Respond with event result. - res.send(event.getRegionInfo()); - }); - - // Login - - httpServer.post("/hk4e_global/mdk/shield/api/login", (req, res) -> { - // Get post data - LoginAccountRequestJson requestData = null; - try { - String body = req.ctx().body(); - requestData = getGsonFactory().fromJson(body, LoginAccountRequestJson.class); - } catch (Exception ignored) { } - - // Create response json - if (requestData == null) { - return; - } - Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", req.ip())); - - res.send(this.getAuthHandler().handleGameLogin(req, requestData)); - }); - - // Login via token - httpServer.post("/hk4e_global/mdk/shield/api/verify", (req, res) -> { - // Get post data - LoginTokenRequestJson requestData = null; - try { - String body = req.ctx().body(); - requestData = getGsonFactory().fromJson(body, LoginTokenRequestJson.class); - } catch (Exception ignored) { - } - - // Create response json - if (requestData == null) { - return; - } - LoginResultJson responseData = new LoginResultJson(); - Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_attempt", req.ip())); - - // Login - Account account = DatabaseHelper.getAccountById(requestData.uid); - - // Test - if (account == null || !account.getSessionKey().equals(requestData.token)) { - responseData.retcode = -111; - responseData.message = translate("messages.dispatch.account.account_cache_error"); - - Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_error", req.ip())); - } else { - responseData.message = "OK"; - responseData.data.account.uid = requestData.uid; - responseData.data.account.token = requestData.token; - responseData.data.account.email = account.getEmail(); - - Grasscutter.getLogger().info(translate("messages.dispatch.account.login_token_success", req.ip(), requestData.uid)); - } - - res.send(responseData); - }); - - // Exchange for combo token - httpServer.post("/hk4e_global/combo/granter/login/v2/login", (req, res) -> { - // Get post data - ComboTokenReqJson requestData = null; - try { - String body = req.ctx().body(); - requestData = getGsonFactory().fromJson(body, ComboTokenReqJson.class); - } catch (Exception ignored) { - } - - // Create response json - if (requestData == null || requestData.data == null) { - return; - } - LoginTokenData loginData = getGsonFactory().fromJson(requestData.data, LoginTokenData.class); // Get login - // data - ComboTokenResJson responseData = new ComboTokenResJson(); - - // Login - Account account = DatabaseHelper.getAccountById(loginData.uid); - - // Test - if (account == null || !account.getSessionKey().equals(loginData.token)) { - responseData.retcode = -201; - responseData.message = translate("messages.dispatch.account.session_key_error"); - - Grasscutter.getLogger().info(translate("messages.dispatch.account.combo_token_error", req.ip())); - } else { - responseData.message = "OK"; - responseData.data.open_id = loginData.uid; - responseData.data.combo_id = "157795300"; - responseData.data.combo_token = account.generateLoginToken(); - - Grasscutter.getLogger().info(translate("messages.dispatch.account.combo_token_success", req.ip())); - } - - res.send(responseData); - }); - - // TODO: There are some missing route request types here (You can tell if they are missing if they are .all and not anything else) - // When http requests for theses routes are found please remove it from the list in DispatchHttpJsonHandler and update the route request types here - - // Agreement and Protocol - // hk4e-sdk-os.hoyoverse.com - httpServer.get("/hk4e_global/mdk/agreement/api/getAgreementInfos", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"marketing_agreements\":[]}}")); - // hk4e-sdk-os.hoyoverse.com - // this could be either GET or POST based on the observation of different clients - httpServer.all("/hk4e_global/combo/granter/api/compareProtocolVersion", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"modified\":true,\"protocol\":{\"id\":0,\"app_id\":4,\"language\":\"en\",\"user_proto\":\"\",\"priv_proto\":\"\",\"major\":7,\"minimum\":0,\"create_time\":\"0\",\"teenager_proto\":\"\",\"third_proto\":\"\"}}}")); - - // Game data - // hk4e-api-os.hoyoverse.com - httpServer.all("/common/hk4e_global/announcement/api/getAlertPic", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}")); - // hk4e-api-os.hoyoverse.com - httpServer.all("/common/hk4e_global/announcement/api/getAlertAnn", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}")); - // hk4e-api-os.hoyoverse.com - httpServer.all("/common/hk4e_global/announcement/api/getAnnList", new AnnouncementHandler()); - // hk4e-api-os-static.hoyoverse.com - httpServer.all("/common/hk4e_global/announcement/api/getAnnContent", new AnnouncementHandler()); - // hk4e-sdk-os.hoyoverse.com - httpServer.all("/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}")); - - // Captcha - // api-account-os.hoyoverse.com - httpServer.post("/account/risky/api/check", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"none\",\"action\":\"ACTION_NONE\",\"geetest\":null}}")); - - // Config - // sdk-os-static.hoyoverse.com - httpServer.get("/combo/box/api/config/sdk/combo", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"vals\":{\"disable_email_bind_skip\":\"false\",\"email_bind_remind_interval\":\"7\",\"email_bind_remind\":\"true\"}}}")); - // hk4e-sdk-os-static.hoyoverse.com - httpServer.get("/hk4e_global/combo/granter/api/getConfig", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"protocol\":true,\"qr_enabled\":false,\"log_level\":\"INFO\",\"announce_url\":\"https://webstatic-sea.hoyoverse.com/hk4e/announcement/index.html?sdk_presentation_style=fullscreen\\u0026sdk_screen_transparent=true\\u0026game_biz=hk4e_global\\u0026auth_appid=announcement\\u0026game=hk4e#/\",\"push_alias_type\":2,\"disable_ysdk_guard\":false,\"enable_announce_pic_popup\":true}}")); - // hk4e-sdk-os-static.hoyoverse.com - httpServer.get("/hk4e_global/mdk/shield/api/loadConfig", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":6,\"game_key\":\"hk4e_global\",\"client\":\"PC\",\"identity\":\"I_IDENTITY\",\"guest\":false,\"ignore_versions\":\"\",\"scene\":\"S_NORMAL\",\"name\":\"原神海外\",\"disable_regist\":false,\"enable_email_captcha\":false,\"thirdparty\":[\"fb\",\"tw\"],\"disable_mmt\":false,\"server_guest\":false,\"thirdparty_ignore\":{\"tw\":\"\",\"fb\":\"\"},\"enable_ps_bind_account\":false,\"thirdparty_login_configs\":{\"tw\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800},\"fb\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800}}}}")); - // Test api? - // abtest-api-data-sg.hoyoverse.com - httpServer.post("/data_abtest_api/config/experiment/list", new DispatchHttpJsonHandler("{\"retcode\":0,\"success\":true,\"message\":\"\",\"data\":[{\"code\":1000,\"type\":2,\"config_id\":\"14\",\"period_id\":\"6036_99\",\"version\":\"1\",\"configs\":{\"cardType\":\"old\"}}]}")); - - // log-upload-os.mihoyo.com - httpServer.all("/log/sdk/upload", new DispatchHttpJsonHandler("{\"code\":0}")); - httpServer.all("/sdk/upload", new DispatchHttpJsonHandler("{\"code\":0}")); - httpServer.post("/sdk/dataUpload", new DispatchHttpJsonHandler("{\"code\":0}")); - // /perf/config/verify?device_id=xxx&platform=x&name=xxx - httpServer.all("/perf/config/verify", new DispatchHttpJsonHandler("{\"code\":0}")); - - // Logging servers - // overseauspider.yuanshen.com - httpServer.all("/log", new ClientLogHandler()); - // log-upload-os.mihoyo.com - httpServer.all("/crash/dataUpload", new ClientLogHandler()); - - // webstatic-sea.hoyoverse.com - httpServer.get("/admin/mi18n/plat_oversea/m202003048/m202003048-version.json", new DispatchHttpJsonHandler("{\"version\":51}")); - - // gacha record. - String gachaMappingsPath = Utils.toFilePath(DATA("/gacha_mappings.js")); - // TODO: Only serve the html page and have a subsequent request to fetch the gacha data. - httpServer.get("/gacha", new GachaRecordHandler()); - if(!(new File(gachaMappingsPath).exists())) { - Tools.createGachaMapping(gachaMappingsPath); - } - - httpServer.raw().config.addSinglePageRoot("/gacha/mappings", gachaMappingsPath, Location.EXTERNAL); - - // static file support for plugins - httpServer.raw().config.precompressStaticFiles = false; // If this isn't set to false, files such as images may appear corrupted when serving static files - - httpServer.listen(DISPATCH_INFO.bindPort); - Grasscutter.getLogger().info(translate("messages.dispatch.port_bind", Integer.toString(httpServer.raw().port()))); - } - - private Map parseQueryString(String qs) { - Map result = new HashMap<>(); - if (qs == null) { - return result; - } - - int last = 0, next, l = qs.length(); - while (last < l) { - next = qs.indexOf('&', last); - if (next == -1) { - next = l; - } - - if (next > last) { - int eqPos = qs.indexOf('=', last); - if (eqPos < 0 || eqPos > next) { - result.put(URLDecoder.decode(qs.substring(last, next), StandardCharsets.UTF_8), ""); - } else { - result.put(URLDecoder.decode(qs.substring(last, eqPos), StandardCharsets.UTF_8), - URLDecoder.decode(qs.substring(eqPos + 1, next), StandardCharsets.UTF_8)); - } - } - last = next + 1; - } - return result; - } - - public AuthenticationHandler getAuthHandler() { - if(authHandler == null) { - return new DefaultAuthenticationHandler(); - } - return authHandler; - } - - public boolean registerAuthHandler(AuthenticationHandler authHandler) { - if(this.authHandler != null) { - Grasscutter.getLogger().error(String.format("[Dispatch] Unable to register '%s' authentication handler. \n" + - "The '%s' authentication handler has already been registered", authHandler.getClass().getName(), this.authHandler.getClass().getName())); - return false; - } - this.authHandler = authHandler; - return true; - } - - public void resetAuthHandler() { - this.authHandler = null; - } - - public static class RegionData { - QueryCurrRegionHttpRsp parsedRegionQuery; - String Base64; - - public RegionData(QueryCurrRegionHttpRsp prq, String b64) { - this.parsedRegionQuery = prq; - this.Base64 = b64; - } - - public QueryCurrRegionHttpRsp getParsedRegionQuery() { - return parsedRegionQuery; - } - - public String getBase64() { - return Base64; - } - } -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java b/src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java deleted file mode 100644 index 92a2961ea..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java +++ /dev/null @@ -1,16 +0,0 @@ -package emu.grasscutter.server.dispatch.authentication; - -import emu.grasscutter.server.dispatch.json.LoginAccountRequestJson; -import emu.grasscutter.server.dispatch.json.LoginResultJson; -import express.http.Request; -import express.http.Response; - -public interface AuthenticationHandler { - - // This is in case plugins also want some sort of authentication - void handleLogin(Request req, Response res); - void handleRegister(Request req, Response res); - void handleChangePassword(Request req, Response res); - - LoginResultJson handleGameLogin(Request req, LoginAccountRequestJson requestData); -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java b/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java deleted file mode 100644 index 67b3d4023..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java +++ /dev/null @@ -1,80 +0,0 @@ -package emu.grasscutter.server.dispatch.authentication; - -import emu.grasscutter.Grasscutter; -import emu.grasscutter.database.DatabaseHelper; -import emu.grasscutter.game.Account; -import emu.grasscutter.server.dispatch.json.LoginAccountRequestJson; -import emu.grasscutter.server.dispatch.json.LoginResultJson; -import express.http.Request; -import express.http.Response; - -import static emu.grasscutter.utils.Language.translate; -import static emu.grasscutter.Configuration.*; - -public class DefaultAuthenticationHandler implements AuthenticationHandler { - - @Override - public void handleLogin(Request req, Response res) { - res.send("Authentication is not available with the default authentication method"); - } - - @Override - public void handleRegister(Request req, Response res) { - res.send("Authentication is not available with the default authentication method"); - } - - @Override - public void handleChangePassword(Request req, Response res) { - res.send("Authentication is not available with the default authentication method"); - } - - @Override - public LoginResultJson handleGameLogin(Request req, LoginAccountRequestJson requestData) { - LoginResultJson responseData = new LoginResultJson(); - - // Login - Account account = DatabaseHelper.getAccountByName(requestData.account); - - // Check if account exists, else create a new one. - if (account == null) { - // Account doesn't exist, so we can either auto create it if the config value is set. - if (ACCOUNT.autoCreate) { - // This account has been created AUTOMATICALLY. There will be no permissions added. - account = DatabaseHelper.createAccountWithId(requestData.account, 0); - - for (String permission : ACCOUNT.defaultPermissions) { - account.addPermission(permission); - } - - if (account != null) { - responseData.message = "OK"; - responseData.data.account.uid = account.getId(); - responseData.data.account.token = account.generateSessionKey(); - responseData.data.account.email = account.getEmail(); - - Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_success", req.ip(), responseData.data.account.uid)); - } else { - responseData.retcode = -201; - responseData.message = translate("messages.dispatch.account.username_create_error"); - - Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_create_error", req.ip())); - } - } else { - responseData.retcode = -201; - responseData.message = translate("messages.dispatch.account.username_error"); - - Grasscutter.getLogger().info(translate("messages.dispatch.account.account_login_exist_error", req.ip())); - } - } else { - // Account was found, log the player in - responseData.message = "OK"; - responseData.data.account.uid = account.getId(); - responseData.data.account.token = account.generateSessionKey(); - responseData.data.account.email = account.getEmail(); - - Grasscutter.getLogger().info(translate("messages.dispatch.account.login_success", req.ip(), responseData.data.account.uid)); - } - - return responseData; - } -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java b/src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java deleted file mode 100644 index b90510367..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/http/GachaRecordHandler.java +++ /dev/null @@ -1,54 +0,0 @@ -package emu.grasscutter.server.dispatch.http; - -import java.io.File; -import java.io.IOException; - -import emu.grasscutter.database.DatabaseHelper; -import emu.grasscutter.game.Account; -import emu.grasscutter.utils.FileUtils; -import emu.grasscutter.utils.Utils; -import express.http.HttpContextHandler; -import express.http.Request; -import express.http.Response; - -import static emu.grasscutter.Configuration.*; - -public final class GachaRecordHandler implements HttpContextHandler { - String render_template; - public GachaRecordHandler() { - File template = new File(Utils.toFilePath(DATA("/gacha_records.html"))); - if (template.exists()) { - // Load from cache - render_template = new String(FileUtils.read(template)); - } else { - render_template = "{{REPLACE_RECORD}}"; - } - } - - @Override - public void handle(Request req, Response res) throws IOException { - // Grasscutter.getLogger().info( req.query().toString() ); - String sessionKey = req.query("s"); - int page = 0; - int gachaType = 0; - if (req.query("p") != null) { - page = Integer.parseInt(req.query("p")); - } - - if (req.query("gachaType") != null) { - gachaType = Integer.parseInt(req.query("gachaType")); - } - - Account account = DatabaseHelper.getAccountBySessionKey(sessionKey); - if (account != null) { - String records = DatabaseHelper.getGachaRecords(account.getPlayerUid(), page, gachaType).toString(); - // Grasscutter.getLogger().info(records); - String response = render_template.replace("{{REPLACE_RECORD}}", records) - .replace("{{REPLACE_MAXPAGE}}", String.valueOf(DatabaseHelper.getGachaRecordsMaxPage(account.getPlayerUid(), page, gachaType))); - - res.send(response); - } else { - res.send("No account found."); - } - } -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenReqJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenReqJson.java deleted file mode 100644 index b3497f8d4..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenReqJson.java +++ /dev/null @@ -1,15 +0,0 @@ -package emu.grasscutter.server.dispatch.json; - -public class ComboTokenReqJson { - public int app_id; - public int channel_id; - public String data; - public String device; - public String sign; - - public static class LoginTokenData { - public String uid; - public String token; - public boolean guest; - } -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenResJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenResJson.java deleted file mode 100644 index 7c49d1278..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/json/ComboTokenResJson.java +++ /dev/null @@ -1,17 +0,0 @@ -package emu.grasscutter.server.dispatch.json; - -public class ComboTokenResJson { - public String message; - public int retcode; - public LoginData data = new LoginData(); - - public static class LoginData { - public int account_type = 1; - public boolean heartbeat; - public String combo_id; - public String combo_token; - public String open_id; - public String data = "{\"guest\":false}"; - public String fatigue_remind = null; // ? - } -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/LoginAccountRequestJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/LoginAccountRequestJson.java deleted file mode 100644 index cb3aff349..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/json/LoginAccountRequestJson.java +++ /dev/null @@ -1,7 +0,0 @@ -package emu.grasscutter.server.dispatch.json; - -public class LoginAccountRequestJson { - public String account; - public String password; - public boolean is_crypto; -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/LoginResultJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/LoginResultJson.java deleted file mode 100644 index 1f4dcd4b4..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/json/LoginResultJson.java +++ /dev/null @@ -1,38 +0,0 @@ -package emu.grasscutter.server.dispatch.json; - -public class LoginResultJson { - public String message; - public int retcode; - public VerifyData data = new VerifyData(); - - public static class VerifyData { - public VerifyAccountData account = new VerifyAccountData(); - public boolean device_grant_required = false; - public String realname_operation = "NONE"; - public boolean realperson_required = false; - public boolean safe_mobile_required = false; - } - - public static class VerifyAccountData { - public String uid; - public String name = ""; - public String email = ""; - public String mobile = ""; - public String is_email_verify = "0"; - public String realname = ""; - public String identity_card = ""; - public String token; - public String safe_mobile = ""; - public String facebook_name = ""; - public String twitter_name = ""; - public String game_center_name = ""; - public String google_name = ""; - public String apple_name = ""; - public String sony_name = ""; - public String tap_name = ""; - public String country = "US"; - public String reactivate_ticket = ""; - public String area_code = "**"; - public String device_grant_ticket = ""; - } -} diff --git a/src/main/java/emu/grasscutter/server/dispatch/json/LoginTokenRequestJson.java b/src/main/java/emu/grasscutter/server/dispatch/json/LoginTokenRequestJson.java deleted file mode 100644 index 12fed8f09..000000000 --- a/src/main/java/emu/grasscutter/server/dispatch/json/LoginTokenRequestJson.java +++ /dev/null @@ -1,6 +0,0 @@ -package emu.grasscutter.server.dispatch.json; - -public class LoginTokenRequestJson { - public String uid; - public String token; -} diff --git a/src/main/java/emu/grasscutter/server/http/HttpServer.java b/src/main/java/emu/grasscutter/server/http/HttpServer.java index dc0d396a6..5227d9793 100644 --- a/src/main/java/emu/grasscutter/server/http/HttpServer.java +++ b/src/main/java/emu/grasscutter/server/http/HttpServer.java @@ -162,11 +162,11 @@ public final class HttpServer { - + - + """); diff --git a/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java b/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java index e720a4b15..8b5dbeec7 100644 --- a/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java +++ b/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java @@ -151,8 +151,10 @@ public final class RegionHandler implements Router { // Get region data. String regionData = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="; - if (request.query().values().size() > 0) - regionData = regions.get(regionName).getBase64(); + if (request.query().values().size() > 0) { + var region = regions.get(regionName); + if(region != null) regionData = region.getBase64(); + } // Invoke event. QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); event.call(); @@ -183,4 +185,12 @@ public final class RegionHandler implements Router { return this.base64; } } + + /** + * Gets the current region query. + * @return A {@link QueryCurrRegionHttpRsp} object. + */ + public static QueryCurrRegionHttpRsp getCurrentRegion() { + return SERVER.runMode == ServerRunMode.HYBRID ? regions.get("os_usa").getRegionQuery() : null; + } } diff --git a/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java index a64e0552a..794b88ed4 100644 --- a/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java +++ b/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java @@ -1,7 +1,7 @@ package emu.grasscutter.server.http.handlers; import emu.grasscutter.Grasscutter; -import emu.grasscutter.server.dispatch.DispatchHttpJsonHandler; +import emu.grasscutter.server.http.objects.HttpJsonResponse; import emu.grasscutter.server.http.Router; import express.Express; import express.http.Request; @@ -21,15 +21,15 @@ import static emu.grasscutter.Configuration.DATA; public final class AnnouncementsHandler implements Router { @Override public void applyRoutes(Express express, Javalin handle) { // hk4e-api-os.hoyoverse.com - express.all("/common/hk4e_global/announcement/api/getAlertPic", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}")); + express.all("/common/hk4e_global/announcement/api/getAlertPic", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}")); // hk4e-api-os.hoyoverse.com - express.all("/common/hk4e_global/announcement/api/getAlertAnn", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}")); + express.all("/common/hk4e_global/announcement/api/getAlertAnn", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}")); // hk4e-api-os.hoyoverse.com express.all("/common/hk4e_global/announcement/api/getAnnList", AnnouncementsHandler::getAnnouncement); // hk4e-api-os-static.hoyoverse.com express.all("/common/hk4e_global/announcement/api/getAnnContent", AnnouncementsHandler::getAnnouncement); // hk4e-sdk-os.hoyoverse.com - express.all("/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}")); + express.all("/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}")); } private static void getAnnouncement(Request request, Response response) { diff --git a/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java new file mode 100644 index 000000000..6cb27d90d --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java @@ -0,0 +1,71 @@ +package emu.grasscutter.server.http.handlers; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.server.http.Router; +import emu.grasscutter.tools.Tools; +import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.Utils; +import express.Express; +import express.http.Request; +import express.http.Response; +import io.javalin.Javalin; +import io.javalin.http.staticfiles.Location; + +import java.io.File; + +import static emu.grasscutter.Configuration.DATA; + +/** + * Handles all gacha-related HTTP requests. + */ +public final class GachaHandler implements Router { + private final String gachaMappings; + + private static String frontendTemplate = "{{REPLACE_RECORD}}"; + + public GachaHandler() { + this.gachaMappings = Utils.toFilePath(DATA("/gacha_mappings.js")); + if(!(new File(this.gachaMappings).exists())) { + try { + Tools.createGachaMapping(this.gachaMappings); + } catch (Exception exception) { + Grasscutter.getLogger().warn("Failed to create gacha mappings.", exception); + } + } + + var templateFile = new File(DATA("/gacha_records.html")); + if(templateFile.exists()) + frontendTemplate = new String(FileUtils.read(templateFile)); + } + + @Override public void applyRoutes(Express express, Javalin handle) { + express.get("/gacha", GachaHandler::gachaRecords); + + express.useStaticFallback("/gacha/mappings", this.gachaMappings, Location.EXTERNAL); + } + + private static void gachaRecords(Request request, Response response) { + var sessionKey = request.query("s"); + + int page = 0, gachaType = 0; + if(request.query("p") != null) + page = Integer.parseInt(request.query("p")); + if(request.query("gachaType") != null) + gachaType = Integer.parseInt(request.query("gachaType")); + + // Get account from session key. + var account = DatabaseHelper.getAccountBySessionKey(sessionKey); + + if(account == null) // Send response. + response.status(404).send("Unable to find account."); + else { + String records = DatabaseHelper.getGachaRecords(account.getPlayerUid(), gachaType, page).toString(); + long maxPage = DatabaseHelper.getGachaRecordsMaxPage(account.getPlayerUid(), page, gachaType); + + response.send(frontendTemplate + .replace("{{REPLACE_RECORD}}", records) + .replace("{{REPLACE_MAXPAGE}}", String.valueOf(maxPage))); + } + } +} diff --git a/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java index bb0bc8eea..2de8969d7 100644 --- a/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java +++ b/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java @@ -1,8 +1,12 @@ package emu.grasscutter.server.http.handlers; -import emu.grasscutter.server.dispatch.DispatchHttpJsonHandler; +import emu.grasscutter.GameConstants; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.server.http.objects.HttpJsonResponse; import emu.grasscutter.server.http.Router; import express.Express; +import express.http.Request; +import express.http.Response; import io.javalin.Javalin; /** @@ -11,37 +15,41 @@ import io.javalin.Javalin; public final class GenericHandler implements Router { @Override public void applyRoutes(Express express, Javalin handle) { // hk4e-sdk-os.hoyoverse.com - express.get("/hk4e_global/mdk/agreement/api/getAgreementInfos", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"marketing_agreements\":[]}}")); + express.get("/hk4e_global/mdk/agreement/api/getAgreementInfos", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"marketing_agreements\":[]}}")); // hk4e-sdk-os.hoyoverse.com // this could be either GET or POST based on the observation of different clients - express.all("/hk4e_global/combo/granter/api/compareProtocolVersion", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"modified\":true,\"protocol\":{\"id\":0,\"app_id\":4,\"language\":\"en\",\"user_proto\":\"\",\"priv_proto\":\"\",\"major\":7,\"minimum\":0,\"create_time\":\"0\",\"teenager_proto\":\"\",\"third_proto\":\"\"}}}")); + express.all("/hk4e_global/combo/granter/api/compareProtocolVersion", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"modified\":true,\"protocol\":{\"id\":0,\"app_id\":4,\"language\":\"en\",\"user_proto\":\"\",\"priv_proto\":\"\",\"major\":7,\"minimum\":0,\"create_time\":\"0\",\"teenager_proto\":\"\",\"third_proto\":\"\"}}}")); // api-account-os.hoyoverse.com - express.post("/account/risky/api/check", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"none\",\"action\":\"ACTION_NONE\",\"geetest\":null}}")); + express.post("/account/risky/api/check", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"none\",\"action\":\"ACTION_NONE\",\"geetest\":null}}")); // sdk-os-static.hoyoverse.com - express.get("/combo/box/api/config/sdk/combo", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"vals\":{\"disable_email_bind_skip\":\"false\",\"email_bind_remind_interval\":\"7\",\"email_bind_remind\":\"true\"}}}")); + express.get("/combo/box/api/config/sdk/combo", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"vals\":{\"disable_email_bind_skip\":\"false\",\"email_bind_remind_interval\":\"7\",\"email_bind_remind\":\"true\"}}}")); // hk4e-sdk-os-static.hoyoverse.com - express.get("/hk4e_global/combo/granter/api/getConfig", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"protocol\":true,\"qr_enabled\":false,\"log_level\":\"INFO\",\"announce_url\":\"https://webstatic-sea.hoyoverse.com/hk4e/announcement/index.html?sdk_presentation_style=fullscreen\\u0026sdk_screen_transparent=true\\u0026game_biz=hk4e_global\\u0026auth_appid=announcement\\u0026game=hk4e#/\",\"push_alias_type\":2,\"disable_ysdk_guard\":false,\"enable_announce_pic_popup\":true}}")); + express.get("/hk4e_global/combo/granter/api/getConfig", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"protocol\":true,\"qr_enabled\":false,\"log_level\":\"INFO\",\"announce_url\":\"https://webstatic-sea.hoyoverse.com/hk4e/announcement/index.html?sdk_presentation_style=fullscreen\\u0026sdk_screen_transparent=true\\u0026game_biz=hk4e_global\\u0026auth_appid=announcement\\u0026game=hk4e#/\",\"push_alias_type\":2,\"disable_ysdk_guard\":false,\"enable_announce_pic_popup\":true}}")); // hk4e-sdk-os-static.hoyoverse.com - express.get("/hk4e_global/mdk/shield/api/loadConfig", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":6,\"game_key\":\"hk4e_global\",\"client\":\"PC\",\"identity\":\"I_IDENTITY\",\"guest\":false,\"ignore_versions\":\"\",\"scene\":\"S_NORMAL\",\"name\":\"原神海外\",\"disable_regist\":false,\"enable_email_captcha\":false,\"thirdparty\":[\"fb\",\"tw\"],\"disable_mmt\":false,\"server_guest\":false,\"thirdparty_ignore\":{\"tw\":\"\",\"fb\":\"\"},\"enable_ps_bind_account\":false,\"thirdparty_login_configs\":{\"tw\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800},\"fb\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800}}}}")); + express.get("/hk4e_global/mdk/shield/api/loadConfig", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":6,\"game_key\":\"hk4e_global\",\"client\":\"PC\",\"identity\":\"I_IDENTITY\",\"guest\":false,\"ignore_versions\":\"\",\"scene\":\"S_NORMAL\",\"name\":\"原神海外\",\"disable_regist\":false,\"enable_email_captcha\":false,\"thirdparty\":[\"fb\",\"tw\"],\"disable_mmt\":false,\"server_guest\":false,\"thirdparty_ignore\":{\"tw\":\"\",\"fb\":\"\"},\"enable_ps_bind_account\":false,\"thirdparty_login_configs\":{\"tw\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800},\"fb\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800}}}}")); // Test api? // abtest-api-data-sg.hoyoverse.com - express.post("/data_abtest_api/config/experiment/list", new DispatchHttpJsonHandler("{\"retcode\":0,\"success\":true,\"message\":\"\",\"data\":[{\"code\":1000,\"type\":2,\"config_id\":\"14\",\"period_id\":\"6036_99\",\"version\":\"1\",\"configs\":{\"cardType\":\"old\"}}]}")); - - // hk4e-api-os.hoyoverse.com - express.all("/common/hk4e_global/announcement/api/getAlertPic", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}")); - // hk4e-api-os.hoyoverse.com - express.all("/common/hk4e_global/announcement/api/getAlertAnn", new DispatchHttpJsonHandler("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}")); + express.post("/data_abtest_api/config/experiment/list", new HttpJsonResponse("{\"retcode\":0,\"success\":true,\"message\":\"\",\"data\":[{\"code\":1000,\"type\":2,\"config_id\":\"14\",\"period_id\":\"6036_99\",\"version\":\"1\",\"configs\":{\"cardType\":\"old\"}}]}")); // log-upload-os.mihoyo.com - express.all("/log/sdk/upload", new DispatchHttpJsonHandler("{\"code\":0}")); - express.all("/sdk/upload", new DispatchHttpJsonHandler("{\"code\":0}")); - express.post("/sdk/dataUpload", new DispatchHttpJsonHandler("{\"code\":0}")); + express.all("/log/sdk/upload", new HttpJsonResponse("{\"code\":0}")); + express.all("/sdk/upload", new HttpJsonResponse("{\"code\":0}")); + express.post("/sdk/dataUpload", new HttpJsonResponse("{\"code\":0}")); // /perf/config/verify?device_id=xxx&platform=x&name=xxx - express.all("/perf/config/verify", new DispatchHttpJsonHandler("{\"code\":0}")); + express.all("/perf/config/verify", new HttpJsonResponse("{\"code\":0}")); // webstatic-sea.hoyoverse.com - express.get("/admin/mi18n/plat_oversea/m202003048/m202003048-version.json", new DispatchHttpJsonHandler("{\"version\":51}")); + express.get("/admin/mi18n/plat_oversea/m202003048/m202003048-version.json", new HttpJsonResponse("{\"version\":51}")); + + express.get("/status/server", GenericHandler::serverStatus); + } + + private static void serverStatus(Request request, Response response) { + int playerCount = Grasscutter.getGameServer().getPlayers().size(); + String version = GameConstants.VERSION; + + response.send("{\"retcode\":0,\"status\":{\"playerCount\":" + playerCount + ",\"version\":\"" + version + "\"}}"); } } diff --git a/src/main/java/emu/grasscutter/server/http/handlers/LegacyAuthHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/LegacyAuthHandler.java new file mode 100644 index 000000000..943a56d7e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/http/handlers/LegacyAuthHandler.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.http.handlers; + +import emu.grasscutter.server.http.Router; +import express.Express; +import io.javalin.Javalin; + +/** + * Handles the legacy authentication system routes. + */ +public final class LegacyAuthHandler implements Router { + @Override public void applyRoutes(Express express, Javalin handle) { + express.get("/authentication/type", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); + express.post("/authentication/login", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); + express.post("/authentication/register", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); + express.post("/authentication/change_password", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java index 4f52c0826..08025d365 100644 --- a/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java +++ b/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java @@ -1,8 +1,9 @@ package emu.grasscutter.server.http.handlers; -import emu.grasscutter.server.dispatch.ClientLogHandler; import emu.grasscutter.server.http.Router; import express.Express; +import express.http.Request; +import express.http.Response; import io.javalin.Javalin; /** @@ -11,8 +12,13 @@ import io.javalin.Javalin; public final class LogHandler implements Router { @Override public void applyRoutes(Express express, Javalin handle) { // overseauspider.yuanshen.com - express.post("/log", new ClientLogHandler()); + express.post("/log", LogHandler::log); // log-upload-os.mihoyo.com - express.post("/crash/dataUpload", new ClientLogHandler()); + express.post("/crash/dataUpload", LogHandler::log); + } + + private static void log(Request request, Response response) { + // TODO: Figure out how to dump request body and log to file. + response.send("{\"code\":0}"); } } diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java b/src/main/java/emu/grasscutter/server/http/objects/HttpJsonResponse.java similarity index 90% rename from src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java rename to src/main/java/emu/grasscutter/server/http/objects/HttpJsonResponse.java index 8d1164e8d..35ca9b006 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchHttpJsonHandler.java +++ b/src/main/java/emu/grasscutter/server/http/objects/HttpJsonResponse.java @@ -1,4 +1,4 @@ -package emu.grasscutter.server.dispatch; +package emu.grasscutter.server.http.objects; import java.io.IOException; import java.util.Arrays; @@ -13,7 +13,7 @@ import express.http.Response; import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.Configuration.*; -public final class DispatchHttpJsonHandler implements HttpContextHandler { +public final class HttpJsonResponse implements HttpContextHandler { private final String response; private final String[] missingRoutes = { // TODO: When http requests for theses routes are found please remove it from this list and update the route request type in the DispatchServer "/common/hk4e_global/announcement/api/getAlertPic", @@ -28,7 +28,7 @@ public final class DispatchHttpJsonHandler implements HttpContextHandler { "/crash/dataUpload" }; - public DispatchHttpJsonHandler(String response) { + public HttpJsonResponse(String response) { this.response = response; } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java index 9a21e4143..362493755 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java @@ -9,10 +9,12 @@ import emu.grasscutter.net.proto.PlayerLoginRspOuterClass.PlayerLoginRsp; import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass; import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.http.dispatch.RegionHandler; import emu.grasscutter.utils.FileUtils; import java.io.File; import java.util.Base64; +import java.util.Objects; import static emu.grasscutter.Configuration.*; @@ -55,7 +57,7 @@ public class PacketPlayerLoginRsp extends BasePacket { info = regionCache.getRegionInfo(); } else { - info = Grasscutter.getDispatchServer().getCurrRegion().getRegionInfo(); + info = Objects.requireNonNull(RegionHandler.getCurrentRegion()).getRegionInfo(); } PlayerLoginRsp p = PlayerLoginRsp.newBuilder() From 922359d6e6ba7e87ce7e62a24cb07a678c234712 Mon Sep 17 00:00:00 2001 From: muhammadeko Date: Sat, 14 May 2022 07:50:36 +0700 Subject: [PATCH 148/312] add negative permission check --- src/main/java/emu/grasscutter/game/Account.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java index 6c3daf61a..39fb8969e 100644 --- a/src/main/java/emu/grasscutter/game/Account.java +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -144,12 +144,20 @@ public class Account { } public boolean hasPermission(String permission) { - if (this.permissions.contains(permission) || this.permissions.contains("*")) { + if (this.permissions.contains(permission)) { return true; } + if(this.permissions.contains("*") && this.permissions.contains("-"+permission)) { + return false; + } + String[] permissionParts = permission.split("\\."); for (String p : this.permissions) { + if (p.startsWith("-") && permissionMatchesWildcard(p.substring(1), permissionParts)) { + return false; + } if (permissionMatchesWildcard(p, permissionParts)) { + Grasscutter.getLogger().info("Permission " + p + " matches " + permission); return true; } } From 855a098aa7210ab78bd6d78d7d8855ccd09ee461 Mon Sep 17 00:00:00 2001 From: muhammadeko Date: Sat, 14 May 2022 11:32:33 +0700 Subject: [PATCH 149/312] fix logic and some cleaning --- .../java/emu/grasscutter/game/Account.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java index 39fb8969e..518bd4786 100644 --- a/src/main/java/emu/grasscutter/game/Account.java +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -144,24 +144,22 @@ public class Account { } public boolean hasPermission(String permission) { - if (this.permissions.contains(permission)) { - return true; - } - if(this.permissions.contains("*") && this.permissions.contains("-"+permission)) { - return false; - } + + if (this.permissions.contains(permission)) return true; + if(this.permissions.contains("*") && this.permissions.size() == 1) return true; String[] permissionParts = permission.split("\\."); for (String p : this.permissions) { + if (p.startsWith("-") && permissionMatchesWildcard(p.substring(1), permissionParts)) { + Grasscutter.getLogger().info("Permission " + permission + " denied to " + this.username); return false; } - if (permissionMatchesWildcard(p, permissionParts)) { - Grasscutter.getLogger().info("Permission " + p + " matches " + permission); - return true; - } + + if (permissionMatchesWildcard(p, permissionParts)) return true; } - return false; + + return this.permissions.contains("*"); } public boolean removePermission(String permission) { From 4f553f6694f53dba4c0b5bba6ffa6caee72a2b9f Mon Sep 17 00:00:00 2001 From: muhammadeko Date: Sat, 14 May 2022 11:34:09 +0700 Subject: [PATCH 150/312] remove log --- src/main/java/emu/grasscutter/game/Account.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java index 518bd4786..84873ec61 100644 --- a/src/main/java/emu/grasscutter/game/Account.java +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -150,12 +150,7 @@ public class Account { String[] permissionParts = permission.split("\\."); for (String p : this.permissions) { - - if (p.startsWith("-") && permissionMatchesWildcard(p.substring(1), permissionParts)) { - Grasscutter.getLogger().info("Permission " + permission + " denied to " + this.username); - return false; - } - + if (p.startsWith("-") && permissionMatchesWildcard(p.substring(1), permissionParts)) return false; if (permissionMatchesWildcard(p, permissionParts)) return true; } From 3c654cf0302b4b43b0315ae39607fccf5bcf5951 Mon Sep 17 00:00:00 2001 From: luluxiaoyu <58876608+luluxiaoyu@users.noreply.github.com> Date: Sat, 14 May 2022 09:30:40 +0800 Subject: [PATCH 151/312] Update zh-TW.json --- src/main/resources/languages/zh-TW.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index 2b6fe34ff..caf37d0d7 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -215,6 +215,14 @@ "success": "座標:%s, %s, %s\n場景ID:%s", "description": "獲取目前所在位置的座標。" }, + "quest": { + "description": "添加或完成任務", + "usage": "quest [任務ID]", + "added": "已添加任務 %s", + "finished": "已完成任務 %s", + "not_found": "未找到任務", + "invalid_id": "無效的任務ID" + }, "reload": { "reload_start": "正在重新加載設定檔。", "reload_done": "重新加載已完成。", From 470007a6c0bc9b117985d4de973b7193d0ff737f Mon Sep 17 00:00:00 2001 From: zrll_ <46812903+zrll12@users.noreply.github.com> Date: Sat, 14 May 2022 15:12:57 +0800 Subject: [PATCH 152/312] Fix connot execute quest command in console --- .../java/emu/grasscutter/command/commands/QuestCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/commands/QuestCommand.java b/src/main/java/emu/grasscutter/command/commands/QuestCommand.java index 70fae0120..affbfa769 100644 --- a/src/main/java/emu/grasscutter/command/commands/QuestCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/QuestCommand.java @@ -37,7 +37,7 @@ public final class QuestCommand implements CommandHandler { switch (cmd) { case "add" -> { - GameQuest quest = sender.getQuestManager().addQuest(questId); + GameQuest quest = targetPlayer.getQuestManager().addQuest(questId); if (quest != null) { CommandHandler.sendMessage(sender, translate(sender, "commands.quest.added", questId)); @@ -47,7 +47,7 @@ public final class QuestCommand implements CommandHandler { CommandHandler.sendMessage(sender, translate(sender, "commands.quest.not_found")); } case "finish" -> { - GameQuest quest = sender.getQuestManager().getQuestById(questId); + GameQuest quest = targetPlayer.getQuestManager().getQuestById(questId); if (quest == null) { CommandHandler.sendMessage(sender, translate(sender, "commands.quest.not_found")); From 741e5749ad4b242efce1ad42dea903136f7ccd4f Mon Sep 17 00:00:00 2001 From: lsCoding666 <73888354+lsCoding666@users.noreply.github.com> Date: Sat, 14 May 2022 21:28:02 +0800 Subject: [PATCH 153/312] new command join and remove to force join or remove avatars into your current team (#549) * feat:new command "join" and "remove" to force join or remove avatar in your current team * fix:change MaxAvatarsInTeam from 9 to 4 * feat:update & merge branch.Translate fix --- .../command/commands/JoinCommand.java | 46 +++++++++++++++++++ .../command/commands/RemoveCommand.java | 43 +++++++++++++++++ src/main/resources/languages/en-US.json | 9 ++++ src/main/resources/languages/zh-CN.json | 9 ++++ src/main/resources/languages/zh-TW.json | 5 ++ 5 files changed, 112 insertions(+) create mode 100644 src/main/java/emu/grasscutter/command/commands/JoinCommand.java create mode 100644 src/main/java/emu/grasscutter/command/commands/RemoveCommand.java diff --git a/src/main/java/emu/grasscutter/command/commands/JoinCommand.java b/src/main/java/emu/grasscutter/command/commands/JoinCommand.java new file mode 100644 index 000000000..4dac15dd7 --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/JoinCommand.java @@ -0,0 +1,46 @@ +package emu.grasscutter.command.commands; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.server.packet.send.PacketChangeMpTeamAvatarRsp; + +import java.util.ArrayList; +import java.util.List; + +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "join", usage = "join [AvatarIDs] such as\"join 10000038 10000039\"", + description = "commands.join.description", permission = "player.join") +public class JoinCommand implements CommandHandler { + + @Override + public void execute(Player sender, Player targetPlayer, List args) { + List avatarIds = new ArrayList<>(); + for (String arg : args) { + try { + int avatarId = Integer.parseInt(arg); + avatarIds.add(avatarId); + } catch (Exception ignored) { + ignored.printStackTrace(); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.avatarId")); + return; + } + } + + + for (int i = 0; i < args.size(); i++) { + Avatar avatar = sender.getAvatars().getAvatarById(avatarIds.get(i)); + if (avatar == null || sender.getTeamManager().getCurrentTeamInfo().contains(avatar)) { + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.avatarId")); + return; + } + sender.getTeamManager().getCurrentTeamInfo().addAvatar(avatar); + } + + // Packet + sender.getTeamManager().updateTeamEntities(new PacketChangeMpTeamAvatarRsp(sender, sender.getTeamManager().getCurrentTeamInfo())); + } +} diff --git a/src/main/java/emu/grasscutter/command/commands/RemoveCommand.java b/src/main/java/emu/grasscutter/command/commands/RemoveCommand.java new file mode 100644 index 000000000..a40b42698 --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/RemoveCommand.java @@ -0,0 +1,43 @@ +package emu.grasscutter.command.commands; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.server.packet.send.PacketChangeMpTeamAvatarRsp; + +import java.util.ArrayList; +import java.util.List; + +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "remove", usage = "remove [indexOfYourTeams] index start from 1", + description = "commands.remove.description", permission = "player.remove") +public class RemoveCommand implements CommandHandler { + + @Override + public void execute(Player sender, Player targetPlayer, List args) { + List avatarIds = new ArrayList<>(); + for (String arg : args) { + try { + int avatarId = Integer.parseInt(arg); + avatarIds.add(avatarId); + } catch (Exception ignored) { + ignored.printStackTrace(); + CommandHandler.sendMessage(sender, translate("commands.remove.invalid_index")); + return; + } + } + + for (int i = 0; i < avatarIds.size(); i++) { + if (avatarIds.get(i) > sender.getTeamManager().getCurrentTeamInfo().getAvatars().size() || avatarIds.get(i) <= 0) { + CommandHandler.sendMessage(sender, translate("commands.remove.invalid_index")); + return; + } + sender.getTeamManager().getCurrentTeamInfo().removeAvatar(avatarIds.get(i) - 1); + } + + // Packet + sender.getTeamManager().updateTeamEntities(new PacketChangeMpTeamAvatarRsp(sender, sender.getTeamManager().getCurrentTeamInfo())); + } +} diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 02945e6d4..32631db1f 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -176,6 +176,10 @@ "success": "All characters have been healed.", "description": "Heal all characters in your current team." }, + "join": { + "usage": "Usage: join [AvatarIDs] such as\"join 10000038 10000039\"", + "description": "force join avatar into your team" + }, "kick": { "player_kick_player": "Player [%s:%s] has kicked player [%s:%s]", "server_kick_player": "Kicking player [%s:%s]", @@ -228,6 +232,11 @@ "reload_done": "Reload complete.", "description": "Reload server config" }, + "remove": { + "usage": "Usage: remove [indexOfYourTeams] index start from 1", + "invalid_index": "index start from 1", + "description": "force remove avatar into your team" + }, "resetConst": { "reset_all": "Reset all avatars' constellations.", "success": "Constellations for %s have been reset. Please relog to see changes.", diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index aaeac5a86..5f379540b 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -176,6 +176,10 @@ "success": "已治疗所有角色。", "description": "治疗当前队伍的角色" }, + "join": { + "usage": "用法:join <角色IDs> 例如\"join 10000038 10000039\"空格分开", + "description": "强制将角色加入到当前队伍中" + }, "kick": { "player_kick_player": "玩家 [%s:%s] 已将 [%s:%s] 踢出。", "server_kick_player": "正在踢出玩家 [%s:%s]...", @@ -228,6 +232,11 @@ "reload_done": "重载完成。", "description": "重载配置文件和数据" }, + "remove": { + "usage": "用法: remove [indexOfYourTeams] 从1开始", + "invalid_index": "下标从1开始", + "description": "强制移除队内角色" + }, "resetConst": { "reset_all": "重置所有角色的命座。", "success": "已重置 %s 的命座,重新登录后生效。", diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index caf37d0d7..25f92f869 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -228,6 +228,11 @@ "reload_done": "重新加載已完成。", "description": "重新加載設定檔和數據。" }, + "remove": { + "usage": "用法: remove [indexOfYourTeams] 从1开始", + "invalid_index": "下標從1開始", + "description": "强制移除對内角色" + }, "resetConst": { "reset_all": "重設所有角色的命座。", "success": "已重設 %s 的命座,重新登入後將會生效。", From 2e7cd0b46f2d64967c83e99913846e2f30d1908a Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Sat, 14 May 2022 12:10:43 -0400 Subject: [PATCH 154/312] Fix errors --- src/main/java/emu/grasscutter/Grasscutter.java | 1 + src/main/java/emu/grasscutter/game/gacha/GachaBanner.java | 6 +++--- .../server/packet/send/PacketPlayerLoginRsp.java | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index fa37772ef..327aa174c 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -6,6 +6,7 @@ import java.util.Calendar; import emu.grasscutter.auth.AuthenticationSystem; import emu.grasscutter.auth.DefaultAuthentication; import emu.grasscutter.command.CommandMap; +import emu.grasscutter.game.managers.StaminaManager.StaminaManager; import emu.grasscutter.plugin.PluginManager; import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.scripts.ScriptLoader; diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java index 7b498a80f..d3b5d8959 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -140,9 +140,9 @@ public class GachaBanner { + lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":" + lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort) + "/gacha?s=" + sessionKey + "&gachaType=" + gachaType; - String details = "http" + (DISPATCH_INFO.encryption.useInRouting ? "s" : "") + "://" - + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" - + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + String details = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://" + + lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":" + + lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort) + "/gacha/details?s=" + sessionKey + "&gachaType=" + gachaType; // Grasscutter.getLogger().info("record = " + record); diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java index db66554f9..52a487d55 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerLoginRsp.java @@ -9,10 +9,13 @@ import emu.grasscutter.net.proto.PlayerLoginRspOuterClass.PlayerLoginRsp; import emu.grasscutter.net.proto.QueryCurrRegionHttpRspOuterClass; import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.http.dispatch.RegionHandler; +import emu.grasscutter.utils.Crypto; import emu.grasscutter.utils.FileUtils; import java.io.File; import java.util.Base64; +import java.util.Objects; import static emu.grasscutter.Configuration.*; From 0dcf0862f8cd900fb4fc318ba234d3f53afdf97b Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Sat, 14 May 2022 12:13:41 -0400 Subject: [PATCH 155/312] JavaDoc Fix --- src/main/java/emu/grasscutter/auth/AuthenticationSystem.java | 1 - src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java b/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java index 312fdad54..096c4124c 100644 --- a/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java +++ b/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java @@ -54,7 +54,6 @@ public interface AuthenticationSystem { /** * A data container that holds relevant data for authenticating a client. - * Call {@link AuthenticationRequest#builder()} to create a builder. */ @Builder @AllArgsConstructor @Getter class AuthenticationRequest { diff --git a/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java b/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java index 298d24493..0239b6e09 100644 --- a/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java +++ b/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java @@ -15,7 +15,7 @@ import static emu.grasscutter.utils.Language.translate; public final class DefaultAuthenticators { /** - * Handles the authentication request from the username & password form. + * Handles the authentication request from the username and password form. */ public static class PasswordAuthenticator implements Authenticator { @Override public LoginResultJson authenticate(AuthenticationRequest request) { From 5d7edc389e6449b21555b771682ed397c98cbc7d Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Sat, 14 May 2022 12:21:55 -0400 Subject: [PATCH 156/312] Implement PR #657 --- .../java/emu/grasscutter/Configuration.java | 2 + .../grasscutter/server/http/HttpServer.java | 44 ++++++++++++++----- .../grasscutter/utils/ConfigContainer.java | 8 +++- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/main/java/emu/grasscutter/Configuration.java b/src/main/java/emu/grasscutter/Configuration.java index 52bfa65aa..4cbd0130c 100644 --- a/src/main/java/emu/grasscutter/Configuration.java +++ b/src/main/java/emu/grasscutter/Configuration.java @@ -1,6 +1,7 @@ package emu.grasscutter; import emu.grasscutter.utils.ConfigContainer; +import emu.grasscutter.utils.ConfigContainer.*; import java.util.Locale; @@ -40,6 +41,7 @@ public final class Configuration extends ConfigContainer { public static final Encryption HTTP_ENCRYPTION = config.server.http.encryption; public static final Policies HTTP_POLICIES = config.server.http.policies; + public static final Files HTTP_STATIC_FILES = config.server.http.files; public static final GameOptions GAME_OPTIONS = config.server.game.gameOptions; public static final GameOptions.InventoryLimits INVENTORY_LIMITS = config.server.game.gameOptions.inventoryLimits; diff --git a/src/main/java/emu/grasscutter/server/http/HttpServer.java b/src/main/java/emu/grasscutter/server/http/HttpServer.java index 5227d9793..898a3a17e 100644 --- a/src/main/java/emu/grasscutter/server/http/HttpServer.java +++ b/src/main/java/emu/grasscutter/server/http/HttpServer.java @@ -2,13 +2,16 @@ package emu.grasscutter.server.http; import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter.ServerDebugMode; +import emu.grasscutter.utils.FileUtils; import express.Express; +import express.http.MediaType; import io.javalin.Javalin; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.util.ssl.SslContextFactory; import java.io.File; +import java.io.IOException; import static emu.grasscutter.Configuration.*; import static emu.grasscutter.utils.Language.translate; @@ -62,7 +65,7 @@ public final class HttpServer { var sslContextFactory = new SslContextFactory.Server(); var keystoreFile = new File(HTTP_ENCRYPTION.keystore); - if(!keystoreFile.exists()) {; + if(!keystoreFile.exists()) { HTTP_ENCRYPTION.useEncryption = false; HTTP_ENCRYPTION.useInRouting = false; @@ -137,15 +140,25 @@ public final class HttpServer { */ public static class DefaultRequestRouter implements Router { @Override public void applyRoutes(Express express, Javalin handle) { - express.get("/", (req, res) -> res.send(""" - - - - - - %s - - """.formatted(translate("messages.status.welcome")))); + express.get("/", (request, response) -> { + File file = new File(HTTP_STATIC_FILES.errorFile); + if(!file.exists()) + response.send(""" + + + + + + %s + + """.formatted(translate("messages.status.welcome"))); + else { + final var filePath = file.getPath(); + final MediaType fromExtension = MediaType.getByExtension(filePath.substring(filePath.lastIndexOf(".") + 1)); + response.type((fromExtension != null) ? fromExtension.getMIME() : "text/plain") + .send(FileUtils.read(filePath)); + } + }); } } @@ -158,7 +171,10 @@ public final class HttpServer { if(SERVER.debugLevel == ServerDebugMode.MISSING) Grasscutter.getLogger().info(translate("messages.dispatch.unhandled_request_error", context.method(), context.url())); context.contentType("text/html"); - context.result(""" + + File file = new File(HTTP_STATIC_FILES.errorFile); + if(!file.exists()) + context.result(""" @@ -170,6 +186,12 @@ public final class HttpServer { """); + else { + final var filePath = file.getPath(); + final MediaType fromExtension = MediaType.getByExtension(filePath.substring(filePath.lastIndexOf(".") + 1)); + context.contentType((fromExtension != null) ? fromExtension.getMIME() : "text/plain") + .result(FileUtils.read(filePath)); + } }); } } diff --git a/src/main/java/emu/grasscutter/utils/ConfigContainer.java b/src/main/java/emu/grasscutter/utils/ConfigContainer.java index 5a06b90be..b65fb10db 100644 --- a/src/main/java/emu/grasscutter/utils/ConfigContainer.java +++ b/src/main/java/emu/grasscutter/utils/ConfigContainer.java @@ -17,7 +17,7 @@ import static emu.grasscutter.Grasscutter.config; */ public class ConfigContainer { private static int version() { - return 2; + return 3; } /** @@ -125,6 +125,7 @@ public class ConfigContainer { public Encryption encryption = new Encryption(); public Policies policies = new Policies(); + public Files files = new Files(); } public static class Game { @@ -227,6 +228,11 @@ public class ConfigContainer { public String nickName = "Server"; public String signature = "Welcome to Grasscutter!"; } + + public static class Files { + public String indexFile = "./index.html"; + public String errorFile = "./404.html"; + } /* Objects. */ From 04d9613facceebe270bd24dc404404c55646af5e Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Sat, 14 May 2022 12:39:21 -0400 Subject: [PATCH 157/312] External authentication --- .../java/emu/grasscutter/Grasscutter.java | 2 -- .../auth/AuthenticationSystem.java | 20 +++++++++++ .../auth/DefaultAuthentication.java | 6 ++++ .../auth/DefaultAuthenticators.java | 20 +++++++++++ .../auth/ExternalAuthenticator.java | 33 +++++++++++++++++++ .../server/http/dispatch/DispatchHandler.java | 6 ++++ .../http/handlers/LegacyAuthHandler.java | 17 ---------- 7 files changed, 85 insertions(+), 19 deletions(-) create mode 100644 src/main/java/emu/grasscutter/auth/ExternalAuthenticator.java delete mode 100644 src/main/java/emu/grasscutter/server/http/handlers/LegacyAuthHandler.java diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 327aa174c..bc5144d97 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -35,7 +35,6 @@ import emu.grasscutter.utils.Language; import emu.grasscutter.server.game.GameServer; import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.Crypto; -import emu.grasscutter.BuildConfig; import javax.annotation.Nullable; @@ -129,7 +128,6 @@ public final class Grasscutter { httpServer.addRouter(GenericHandler.class); httpServer.addRouter(AnnouncementsHandler.class); httpServer.addRouter(DispatchHandler.class); - httpServer.addRouter(LegacyAuthHandler.class); httpServer.addRouter(GachaHandler.class); // TODO: find a better place? diff --git a/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java b/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java index 096c4124c..41aba1c8e 100644 --- a/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java +++ b/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java @@ -2,6 +2,7 @@ package emu.grasscutter.auth; import emu.grasscutter.server.http.objects.*; import express.http.Request; +import express.http.Response; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -52,12 +53,20 @@ public interface AuthenticationSystem { */ Authenticator getSessionKeyAuthenticator(); + /** + * This is the authenticator used for handling external authentication requests. + * @return An authenticator. + */ + ExternalAuthenticator getExternalAuthenticator(); + /** * A data container that holds relevant data for authenticating a client. */ @Builder @AllArgsConstructor @Getter class AuthenticationRequest { private final Request request; + @Nullable private final Response response; + @Nullable private final LoginAccountRequestJson passwordRequest; @Nullable private final LoginTokenRequestJson tokenRequest; @Nullable private final ComboTokenReqJson sessionKeyRequest; @@ -104,4 +113,15 @@ public interface AuthenticationSystem { .sessionKeyData(tokenData) .build(); } + + /** + * Generates an authentication request from a {@link Response} object. + * @param request The Express request. + * @param response the Express response. + * @return An authentication request. + */ + static AuthenticationRequest fromExternalRequest(Request request, Response response) { + return AuthenticationRequest.builder().request(request) + .response(response).build(); + } } diff --git a/src/main/java/emu/grasscutter/auth/DefaultAuthentication.java b/src/main/java/emu/grasscutter/auth/DefaultAuthentication.java index b5e853cb0..08958d8e9 100644 --- a/src/main/java/emu/grasscutter/auth/DefaultAuthentication.java +++ b/src/main/java/emu/grasscutter/auth/DefaultAuthentication.java @@ -15,6 +15,7 @@ public final class DefaultAuthentication implements AuthenticationSystem { private final Authenticator passwordAuthenticator = new PasswordAuthenticator(); private final Authenticator tokenAuthenticator = new TokenAuthenticator(); private final Authenticator sessionKeyAuthenticator = new SessionKeyAuthenticator(); + private final ExternalAuthenticator externalAuthenticator = new ExternalAuthentication(); @Override public void createAccount(String username, String password) { @@ -46,4 +47,9 @@ public final class DefaultAuthentication implements AuthenticationSystem { public Authenticator getSessionKeyAuthenticator() { return this.sessionKeyAuthenticator; } + + @Override + public ExternalAuthenticator getExternalAuthenticator() { + return this.externalAuthenticator; + } } diff --git a/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java b/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java index 0239b6e09..e1d5fddf0 100644 --- a/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java +++ b/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java @@ -158,4 +158,24 @@ public final class DefaultAuthenticators { return response; } } + + /** + * Handles authentication requests from external sources. + */ + public static class ExternalAuthentication implements ExternalAuthenticator { + @Override public void handleLogin(AuthenticationRequest request) { + assert request.getResponse() != null; + request.getResponse().send("Authentication is not available with the default authentication method."); + } + + @Override public void handleAccountCreation(AuthenticationRequest request) { + assert request.getResponse() != null; + request.getResponse().send("Authentication is not available with the default authentication method."); + } + + @Override public void handlePasswordReset(AuthenticationRequest request) { + assert request.getResponse() != null; + request.getResponse().send("Authentication is not available with the default authentication method."); + } + } } diff --git a/src/main/java/emu/grasscutter/auth/ExternalAuthenticator.java b/src/main/java/emu/grasscutter/auth/ExternalAuthenticator.java new file mode 100644 index 000000000..6bf78af6e --- /dev/null +++ b/src/main/java/emu/grasscutter/auth/ExternalAuthenticator.java @@ -0,0 +1,33 @@ +package emu.grasscutter.auth; + +import emu.grasscutter.auth.AuthenticationSystem.AuthenticationRequest; + +/** + * Handles authentication via external routes. + */ +public interface ExternalAuthenticator { + + /** + * Called when an external login request is made. + * @param request The authentication request. + */ + void handleLogin(AuthenticationRequest request); + + /** + * Called when an external account creation request is made. + * @param request The authentication request. + * + * For developers: Use {@link AuthenticationRequest#getRequest()} to get the request body. + * Use {@link AuthenticationRequest#getResponse()} to get the response body. + */ + void handleAccountCreation(AuthenticationRequest request); + + /** + * Called when an external password reset request is made. + * @param request The authentication request. + * + * For developers: Use {@link AuthenticationRequest#getRequest()} to get the request body. + * Use {@link AuthenticationRequest#getResponse()} to get the response body. + */ + void handlePasswordReset(AuthenticationRequest request); +} diff --git a/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java b/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java index 22a31fe6a..5b012c4c3 100644 --- a/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java +++ b/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java @@ -24,6 +24,12 @@ public final class DispatchHandler implements Router { express.post("/hk4e_global/mdk/shield/api/verify", DispatchHandler::tokenLogin); // Combo token login (from session key). express.post("/hk4e_global/combo/granter/login/v2/login", DispatchHandler::sessionKeyLogin); + + // External login (from other clients). + express.get("/authentication/type", (request, response) -> response.send(Grasscutter.getAuthenticationSystem().getClass().getSimpleName())); + express.post("/authentication/login", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); + express.post("/authentication/register", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); + express.post("/authentication/change_password", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); } /** diff --git a/src/main/java/emu/grasscutter/server/http/handlers/LegacyAuthHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/LegacyAuthHandler.java deleted file mode 100644 index 943a56d7e..000000000 --- a/src/main/java/emu/grasscutter/server/http/handlers/LegacyAuthHandler.java +++ /dev/null @@ -1,17 +0,0 @@ -package emu.grasscutter.server.http.handlers; - -import emu.grasscutter.server.http.Router; -import express.Express; -import io.javalin.Javalin; - -/** - * Handles the legacy authentication system routes. - */ -public final class LegacyAuthHandler implements Router { - @Override public void applyRoutes(Express express, Javalin handle) { - express.get("/authentication/type", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); - express.post("/authentication/login", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); - express.post("/authentication/register", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); - express.post("/authentication/change_password", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); - } -} \ No newline at end of file From 6ec27cd17a08571fced90364166139d1cb79b8f4 Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Sat, 14 May 2022 12:41:49 -0400 Subject: [PATCH 158/312] Update routes --- .../server/http/dispatch/DispatchHandler.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java b/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java index 5b012c4c3..5f9edcf0a 100644 --- a/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java +++ b/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java @@ -27,9 +27,12 @@ public final class DispatchHandler implements Router { // External login (from other clients). express.get("/authentication/type", (request, response) -> response.send(Grasscutter.getAuthenticationSystem().getClass().getSimpleName())); - express.post("/authentication/login", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); - express.post("/authentication/register", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); - express.post("/authentication/change_password", (request, response) -> response.status(500).send("{\"notice\":\"This API is deprecated.\"}")); + express.post("/authentication/login", (request, response) -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator() + .handleLogin(AuthenticationSystem.fromExternalRequest(request, response))); + express.post("/authentication/register", (request, response) -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator() + .handleAccountCreation(AuthenticationSystem.fromExternalRequest(request, response))); + express.post("/authentication/change_password", (request, response) -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator() + .handlePasswordReset(AuthenticationSystem.fromExternalRequest(request, response))); } /** From 5b3c05a9a0f5ba8c5714a2326ce890f52088d99d Mon Sep 17 00:00:00 2001 From: tiantian520 <64673335+tiantian520tt@users.noreply.github.com> Date: Sun, 15 May 2022 07:06:48 +0800 Subject: [PATCH 159/312] Add a new command "nostamina" (#877) * Add a new command " nostamina\ * Fix * Fix 2 * Renamed some names. * Update zh-CN.json Fix an existing language expression error. --- .../command/commands/NoStaminaCommand.java | 40 +++++++++++++++++++ .../StaminaManager/StaminaManager.java | 5 ++- .../emu/grasscutter/game/player/Player.java | 10 ++++- src/main/resources/languages/en-US.json | 4 ++ src/main/resources/languages/zh-CN.json | 4 ++ 5 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 src/main/java/emu/grasscutter/command/commands/NoStaminaCommand.java diff --git a/src/main/java/emu/grasscutter/command/commands/NoStaminaCommand.java b/src/main/java/emu/grasscutter/command/commands/NoStaminaCommand.java new file mode 100644 index 000000000..2012bde9f --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/NoStaminaCommand.java @@ -0,0 +1,40 @@ +package emu.grasscutter.command.commands; + +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.player.Player; + +import java.util.List; + +import static emu.grasscutter.utils.Language.translate; + + +@Command(label = "nostamina", usage = "nostamina [on|off]", permission = "player.nostamina", permissionTargeted = "player.nostamina.others", description = "commands.nostamina.description") +public final class NoStaminaCommand implements CommandHandler { + public static boolean StaminaState = false; + //Temp Value + @Override + public void execute(Player sender, Player targetPlayer, List args) { + if (targetPlayer == null) { + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); + return; + } + + + if (args.size() == 1) { + switch (args.get(0).toLowerCase()) { + case "on": + StaminaState = true; + break; + case "off": + StaminaState = false; + break; + default: + break; + } + } + targetPlayer.setStamina(StaminaState);//Set + + CommandHandler.sendMessage(sender, translate(sender, "commands.nostamina.success", (StaminaState ? translate(sender, "commands.status.enabled") : translate(sender, "commands.status.disabled")), targetPlayer.getNickname())); + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index 1f452a667..51261ac3f 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -2,6 +2,7 @@ package emu.grasscutter.game.managers.StaminaManager; import ch.qos.logback.classic.Logger; import emu.grasscutter.Grasscutter; +import emu.grasscutter.command.commands.NoStaminaCommand; import emu.grasscutter.data.GameData; import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.GameEntity; @@ -297,9 +298,11 @@ public class StaminaManager { // Returns new stamina and sends PlayerPropNotify or VehicleStaminaNotify public int setStamina(GameSession session, String reason, int newStamina, boolean isCharacterStamina) { - if (!GAME_OPTIONS.staminaUsage) { + // Target Player + if (!GAME_OPTIONS.staminaUsage || session.getPlayer().getStamina()) { newStamina = getMaxCharacterStamina(); } + // set stamina if is character stamina if (isCharacterStamina) { player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index f86e09370..23b32c70f 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -121,6 +121,7 @@ public class Player { private int mainCharacterId; private boolean godmode; + private boolean stamina; private boolean moonCard; private Date moonCardStartTime; private int moonCardDuration; @@ -781,7 +782,14 @@ public class Player { } this.save(); } - + public boolean getStamina() { + // Get Stamina + return stamina; + } + public void setStamina(boolean stamina) { + // Set Stamina + this.stamina = stamina; + } public boolean inGodmode() { return godmode; } diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index b4dbed789..90db92f4d 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -173,6 +173,10 @@ "success": "Godmode is now %s for %s.", "description": "Prevents you from taking damage. Defaults to toggle." }, + "nostamina": { + "success": "NoStamina is now %s for %s.", + "description": "Keep your endurance to the maximum." + }, "heal": { "success": "All characters have been healed.", "description": "Heal all characters in your current team." diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 5f379540b..3f927d2ed 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -145,6 +145,10 @@ "invalid_amount_or_playerId": "无效的数量/玩家ID。", "description": "给予所有物品" }, + "nostamina": { + "success": "NoStamina %s 对于 %s.", + "description": "保持你的体力处于最高状态。" + }, "giveArtifact": { "usage": "用法:giveart|gart [玩家] <圣遗物ID> <主词条ID> [<副词条ID>[,<强化次数>]]... [等级]", "id_error": "无效的圣遗物ID。", From 159feb40647b679dd6fa57ec382980d388e43821 Mon Sep 17 00:00:00 2001 From: HotaruYS <105128850+HotaruYS@users.noreply.github.com> Date: Sun, 15 May 2022 05:05:19 +0200 Subject: [PATCH 160/312] Properly handle static assets for announcements (#891) --- data/GameAnnouncement.json | 6 +- data/GameAnnouncementList.json | 19 ++--- .../http/handlers/AnnouncementsHandler.java | 80 ++++++++----------- 3 files changed, 43 insertions(+), 62 deletions(-) diff --git a/data/GameAnnouncement.json b/data/GameAnnouncement.json index 96c88f17c..57cd72c8e 100644 --- a/data/GameAnnouncement.json +++ b/data/GameAnnouncement.json @@ -1,19 +1,19 @@ { + "t": "{{SYSTEM_TIME}}", "list": [ { "ann_id": 1, "title": "Welcome to Grasscutter!", "subtitle": "Welcome!", - "banner": "https://uploadstatic-sea.mihoyo.com/announcement/2020/09/17/f4aa42d505822805eebf4a55d72a78d8_2755691727027973637.jpg", + "banner": "{{DISPATCH_PUBLIC}}/hk4e/announcement/assets/banner/1.jpg", "content": "

    Hi there!

    First of all, welcome to Grasscutter. If you have any issues, please let us know so that Lawnmower can help you!


    〓Discord〓

    https://discord.gg/T5vZU6UyeG

    〓GitHub〓https://github.com/Grasscutters/Grasscutter", - "lang": "en-US" }, { "ann_id": 2, "title": "How to use announcements", "subtitle": "How to use announcements", - "banner": "https://uploadstatic-sea.mihoyo.com/announcement/2020/09/17/f4aa42d505822805eebf4a55d72a78d8_2755691727027973637.jpg", + "banner": "{{DISPATCH_PUBLIC}}/hk4e/announcement/assets/banner/2.jpg", "content": "

    Announcement content uses HTML. The specific content of the announcement is stored in the program directory GameAnnouncement.json, while GameAnnouncementList.json stores the announcement list data.

    GameAnnouncement

    ParameterDescription
    ann_idUnique ID
    titleTitle shown at the top of the content
    subtitleShort title shown on the left
    bannerImage to display between content and title
    contentContent body in HTML
    langLanguage code for this entry

    GameAnnouncementList

    If you want to add an announcement, please add the list data in the announcement type corresponding to GameAnnouncementList, and finally add the announcement content in GameAnnouncement.

    ", "lang": "en-US" } diff --git a/data/GameAnnouncementList.json b/data/GameAnnouncementList.json index 7464b3b0f..3697703a3 100644 --- a/data/GameAnnouncementList.json +++ b/data/GameAnnouncementList.json @@ -1,15 +1,14 @@ { - "t": "System.currentTimeMillis()", + "t": "{{SYSTEM_TIME}}", "list": [ { "list": [ { "ann_id": 1, - "title": "Welcome to Grasscutter!", "subtitle": "Welcome!", - "banner": "https://uploadstatic-sea.mihoyo.com/announcement/2020/09/22/7d85f19b152d218e73224d7c138a0fd0_5818585260283672899.jpg", - "tag_icon": "https://uploadstatic-sea.mihoyo.com/announcement/2020/03/05/a2588f1a51faee9fa8dfe9aead649dd6_7237021399135895303.png", + "banner": "{{DISPATCH_PUBLIC}}/hk4e/announcement/assets/banner/1.jpg", + "tag_icon": "{{DISPATCH_PUBLIC}}/hk4e/announcement/assets/tag_icon.png", "type": 2, "type_label": "System", "lang": "en-US", @@ -22,8 +21,8 @@ "ann_id": 2, "title": "How to use announcements", "subtitle": "How to use announcements", - "banner": "https://uploadstatic-sea.mihoyo.com/announcement/2020/09/22/7d85f19b152d218e73224d7c138a0fd0_5818585260283672899.jpg", - "tag_icon": "https://uploadstatic-sea.mihoyo.com/announcement/2020/03/05/a2588f1a51faee9fa8dfe9aead649dd6_7237021399135895303.png", + "banner": "{{DISPATCH_PUBLIC}}/hk4e/announcement/assets/banner/2.jpg", + "tag_icon": "{{DISPATCH_PUBLIC}}/hk4e/announcement/assets/tag_icon.png", "type": 2, "type_label": "System", "lang": "en-US", @@ -59,11 +58,5 @@ ], "timezone": -5, "alert": false, - "alert_id": 0, - "pic_list": [], - "pic_total": 0, - "pic_type_list": [], - "pic_alert": false, - "pic_alert_id": 0, - "static_sign": "" + "alert_id": 0 } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java index 1b87225e9..c4776a4b4 100644 --- a/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java +++ b/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java @@ -6,6 +6,7 @@ import emu.grasscutter.server.http.Router; import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.Utils; import express.Express; +import express.http.MediaType; import express.http.Request; import express.http.Response; import io.javalin.Javalin; @@ -13,27 +14,15 @@ import io.javalin.Javalin; import java.io.File; import java.io.FileInputStream; import java.io.IOException; -import java.nio.file.Paths; +import java.nio.charset.StandardCharsets; import java.util.Objects; -import static emu.grasscutter.Configuration.DATA; +import static emu.grasscutter.Configuration.*; /** * Handles requests related to the announcements page. */ public final class AnnouncementsHandler implements Router { - private static String template, swjs, vue; - - public AnnouncementsHandler() { - var templateFile = new File(Utils.toFilePath(DATA("/hk4e/announcement/index.html"))); - var swjsFile = new File(Utils.toFilePath(DATA("/hk4e/announcement/sw.js"))); - var vueFile = new File(Utils.toFilePath(DATA("/hk4e/announcement/vue.min.js"))); - - template = templateFile.exists() ? new String(FileUtils.read(template)) : null; - swjs = swjsFile.exists() ? new String(FileUtils.read(swjs)) : null; - vue = vueFile.exists() ? new String(FileUtils.read(vueFile)) : null; - } - @Override public void applyRoutes(Express express, Javalin handle) { // hk4e-api-os.hoyoverse.com express.all("/common/hk4e_global/announcement/api/getAlertPic", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}")); @@ -47,58 +36,57 @@ public final class AnnouncementsHandler implements Router { express.all("/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}")); express.get("/hk4e/announcement/*", AnnouncementsHandler::getPageResources); - express.get("/sw.js", AnnouncementsHandler::getPageResources); - express.get("/dora/lib/vue/2.6.11/vue.min.js", AnnouncementsHandler::getPageResources); } private static void getAnnouncement(Request request, Response response) { + String data = ""; if (Objects.equals(request.baseUrl(), "/common/hk4e_global/announcement/api/getAnnContent")) { - String data = readToString(Paths.get(DATA("GameAnnouncement.json")).toFile()); - response.send("{\"retcode\":0,\"message\":\"OK\",\"data\":" + data + "}"); + data = readToString(new File(Utils.toFilePath(DATA("GameAnnouncement.json")))); } else if (Objects.equals(request.baseUrl(), "/common/hk4e_global/announcement/api/getAnnList")) { - String data = readToString(Paths.get(DATA("GameAnnouncementList.json")).toFile()) - .replace("System.currentTimeMillis()", String.valueOf(System.currentTimeMillis())); - response.send("{\"retcode\":0,\"message\":\"OK\",\"data\": " + data + "}"); + data = readToString(new File(Utils.toFilePath(DATA("GameAnnouncementList.json")))); + } else { + response.send("{\"retcode\":404,\"message\":\"Unknown request path\"}"); } + + if (data.isEmpty()) { + response.send("{\"retcode\":500,\"message\":\"Unable to fetch requsted content\"}"); + return; + } + + String dispatchDomain = "http" + (HTTP_ENCRYPTION.useInRouting ? "s" : "") + "://" + + lr(HTTP_INFO.accessAddress, HTTP_INFO.bindAddress) + ":" + + lr(HTTP_INFO.accessPort, HTTP_INFO.bindPort); + + data = data + .replace("{{DISPATCH_PUBLIC}}", dispatchDomain) + .replace("{{SYSTEM_TIME}}", String.valueOf(System.currentTimeMillis())); + response.send("{\"retcode\":0,\"message\":\"OK\",\"data\": " + data + "}"); } private static void getPageResources(Request request, Response response) { - var path = request.path(); - switch(path) { - case "/sw.js" -> response.send(swjs); - case "/hk4e/announcement/index.html" -> response.send(template); - case "/dora/lib/vue/2.6.11/vue.min.js" -> response.send(vue); - - default -> { - File renderFile = new File(Utils.toFilePath(DATA(path))); - if(!renderFile.exists()) { - Grasscutter.getLogger().info("File not exist: " + path); - return; - } - - String ext = path.substring(path.lastIndexOf(".") + 1); - if ("css".equals(ext)) { - response.type("text/css"); - response.send(FileUtils.read(renderFile)); - } else { - response.send(FileUtils.read(renderFile)); - } - } + String filename = Utils.toFilePath(DATA(request.path())); + File file = new File(filename); + if (file.exists() && file.isFile()) { + MediaType fromExtension = MediaType.getByExtension(filename.substring(filename.lastIndexOf(".") + 1)); + response.type((fromExtension != null) ? fromExtension.getMIME() : "application/octet-stream"); + response.send(FileUtils.read(file)); + } else { + Grasscutter.getLogger().warn("File does not exist: " + file); + response.status(404); } } @SuppressWarnings("ResultOfMethodCallIgnored") private static String readToString(File file) { - long length = file.length(); - byte[] content = new byte[(int) length]; + byte[] content = new byte[(int) file.length()]; try { FileInputStream in = new FileInputStream(file); in.read(content); in.close(); } catch (IOException ignored) { - Grasscutter.getLogger().warn("File not found: " + file.getAbsolutePath()); + Grasscutter.getLogger().warn("File does not exist: " + file); } - return new String(content); + return new String(content, StandardCharsets.UTF_8); } } From 3a216bf1bb8f0b9bf255d9a047dc34a0f1859c2a Mon Sep 17 00:00:00 2001 From: Hotaru <105128850+HotaruYS@users.noreply.github.com> Date: Sun, 15 May 2022 03:24:34 +0200 Subject: [PATCH 161/312] Move gacha files to separate directory and refactor file serving --- .../details.html} | 2 +- .../records.html} | 19 +++-- .../server/http/handlers/GachaHandler.java | 84 +++++++++++-------- src/main/resources/languages/en-US.json | 3 +- src/main/resources/languages/pl-PL.json | 3 +- src/main/resources/languages/zh-CN.json | 3 +- src/main/resources/languages/zh-TW.json | 3 +- 7 files changed, 63 insertions(+), 54 deletions(-) rename data/{gacha_details.html => gacha/details.html} (98%) rename data/{gacha_records.html => gacha/records.html} (94%) diff --git a/data/gacha_details.html b/data/gacha/details.html similarity index 98% rename from data/gacha_details.html rename to data/gacha/details.html index ccd775ef6..85443d532 100644 --- a/data/gacha_details.html +++ b/data/gacha/details.html @@ -84,7 +84,7 @@ var fiveStarItems = {{FIVE_STARS}}; var fourStarItems = {{FOUR_STARS}}; var threeStarItems = {{THREE_STARS}}; - var lang = "{{LANGUAGE}}"; + var lang = "{{LANGUAGE}}".toLowerCase(); function getNameForId(itemId) { if (mappings[lang] != null && mappings[lang][itemId] != null) { diff --git a/data/gacha_records.html b/data/gacha/records.html similarity index 94% rename from data/gacha_records.html rename to data/gacha/records.html index 7bea40f61..cad1c89d5 100644 --- a/data/gacha_records.html +++ b/data/gacha/records.html @@ -58,7 +58,7 @@