diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index af88b74ac..2642caa20 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -6,8 +6,8 @@ import java.util.Calendar; import emu.grasscutter.auth.AuthenticationSystem; import emu.grasscutter.auth.DefaultAuthentication; import emu.grasscutter.command.CommandMap; -import emu.grasscutter.game.managers.EnergyManager.EnergyManager; -import emu.grasscutter.game.managers.StaminaManager.StaminaManager; +import emu.grasscutter.game.managers.energy.EnergyManager; +import emu.grasscutter.game.managers.stamina.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/command/commands/UnlimitEnergyCommand.java b/src/main/java/emu/grasscutter/command/commands/UnlimitEnergyCommand.java index 943a21ea1..e3f074979 100644 --- a/src/main/java/emu/grasscutter/command/commands/UnlimitEnergyCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/UnlimitEnergyCommand.java @@ -5,7 +5,7 @@ import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandHandler; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.entity.EntityAvatar; -import emu.grasscutter.game.managers.EnergyManager.EnergyManager; +import emu.grasscutter.game.managers.energy.EnergyManager; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.TeamManager; import emu.grasscutter.game.props.ElementType; diff --git a/src/main/java/emu/grasscutter/game/managers/ChatManager/ChatManager.java b/src/main/java/emu/grasscutter/game/managers/chat/ChatManager.java similarity index 97% rename from src/main/java/emu/grasscutter/game/managers/ChatManager/ChatManager.java rename to src/main/java/emu/grasscutter/game/managers/chat/ChatManager.java index 42ff2df41..ba5fa4d0f 100644 --- a/src/main/java/emu/grasscutter/game/managers/ChatManager/ChatManager.java +++ b/src/main/java/emu/grasscutter/game/managers/chat/ChatManager.java @@ -1,4 +1,4 @@ -package emu.grasscutter.game.managers.ChatManager; +package emu.grasscutter.game.managers.chat; import emu.grasscutter.command.CommandMap; import emu.grasscutter.game.player.Player; diff --git a/src/main/java/emu/grasscutter/game/managers/ChatManager/ChatManagerHandler.java b/src/main/java/emu/grasscutter/game/managers/chat/ChatManagerHandler.java similarity index 89% rename from src/main/java/emu/grasscutter/game/managers/ChatManager/ChatManagerHandler.java rename to src/main/java/emu/grasscutter/game/managers/chat/ChatManagerHandler.java index 3239265e2..013b43e47 100644 --- a/src/main/java/emu/grasscutter/game/managers/ChatManager/ChatManagerHandler.java +++ b/src/main/java/emu/grasscutter/game/managers/chat/ChatManagerHandler.java @@ -1,4 +1,4 @@ -package emu.grasscutter.game.managers.ChatManager; +package emu.grasscutter.game.managers.chat; import emu.grasscutter.game.player.Player; import emu.grasscutter.server.game.GameServer; diff --git a/src/main/java/emu/grasscutter/game/managers/DeforestationManager/DeforestationManager.java b/src/main/java/emu/grasscutter/game/managers/deforestation/DeforestationManager.java similarity index 96% rename from src/main/java/emu/grasscutter/game/managers/DeforestationManager/DeforestationManager.java rename to src/main/java/emu/grasscutter/game/managers/deforestation/DeforestationManager.java index ff7c0b1a9..36e1c0e22 100644 --- a/src/main/java/emu/grasscutter/game/managers/DeforestationManager/DeforestationManager.java +++ b/src/main/java/emu/grasscutter/game/managers/deforestation/DeforestationManager.java @@ -1,91 +1,91 @@ -package emu.grasscutter.game.managers.DeforestationManager; - -import java.util.ArrayList; -import java.util.HashMap; - -import dev.morphia.annotations.Transient; -import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.GameData; -import emu.grasscutter.game.entity.EntityItem; -import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.world.Scene; -import emu.grasscutter.net.proto.HitTreeNotifyOuterClass; -import emu.grasscutter.net.proto.VectorOuterClass; -import emu.grasscutter.utils.Position; - -public class DeforestationManager { - final static int RECORD_EXPIRED_SECONDS = 60*5; // 5 min - final static int RECORD_MAX_TIMES = 3; // max number of wood - final static int RECORD_MAX_TIMES_OTHER_HIT_TREE = 10; // if hit 10 times other trees, reset wood - - @Transient private final Player player; - @Transient private final ArrayList currentRecord; - @Transient private final static HashMap ColliderTypeToWoodItemID = new HashMap<>(); - static { - /* define wood types which reflected to item id*/ - ColliderTypeToWoodItemID.put(1,101301); - ColliderTypeToWoodItemID.put(2,101302); - ColliderTypeToWoodItemID.put(3,101303); - ColliderTypeToWoodItemID.put(4,101304); - ColliderTypeToWoodItemID.put(5,101305); - ColliderTypeToWoodItemID.put(6,101306); - ColliderTypeToWoodItemID.put(7,101307); - ColliderTypeToWoodItemID.put(8,101308); - ColliderTypeToWoodItemID.put(9,101309); - ColliderTypeToWoodItemID.put(10,101310); - ColliderTypeToWoodItemID.put(11,101311); - ColliderTypeToWoodItemID.put(12,101312); - } - public DeforestationManager(Player player){ - this.player = player; - this.currentRecord = new ArrayList<>(); - } - public void resetWood(){ - synchronized (currentRecord) { - currentRecord.clear(); - } - } - public void onDeforestationInvoke(HitTreeNotifyOuterClass.HitTreeNotify hit){ - synchronized (currentRecord) { - //Grasscutter.getLogger().info("onDeforestationInvoke! Wood records {}", currentRecord); - VectorOuterClass.Vector hitPosition = hit.getHitPostion(); - int woodType = hit.getWoodType(); - if (ColliderTypeToWoodItemID.containsKey(woodType)) {// is a available wood type - Scene scene = player.getScene(); - int itemId = ColliderTypeToWoodItemID.get(woodType); - int positionHash = hitPosition.hashCode(); - HitTreeRecord record = searchRecord(positionHash); - if (record == null) { - record = new HitTreeRecord(positionHash); - }else{ - currentRecord.remove(record);// move it to last position - } - currentRecord.add(record); - if(currentRecord.size()>RECORD_MAX_TIMES_OTHER_HIT_TREE){ - currentRecord.remove(0); - } - if(record.record()) { - EntityItem entity = new EntityItem(scene, - null, - GameData.getItemDataMap().get(itemId), - new Position(hitPosition.getX(), hitPosition.getY(), hitPosition.getZ()), - 1, - false); - scene.addEntity(entity); - } - //record.record()=false : too many wood they have deforested, no more wood dropped! - } else { - Grasscutter.getLogger().warn("No wood type {} found.", woodType); - } - } - // unknown wood type - } - private HitTreeRecord searchRecord(int id){ - for (HitTreeRecord record : currentRecord) { - if (record.getUnique() == id) { - return record; - } - } - return null; - } -} +package emu.grasscutter.game.managers.deforestation; + +import java.util.ArrayList; +import java.util.HashMap; + +import dev.morphia.annotations.Transient; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.entity.EntityItem; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.world.Scene; +import emu.grasscutter.net.proto.HitTreeNotifyOuterClass; +import emu.grasscutter.net.proto.VectorOuterClass; +import emu.grasscutter.utils.Position; + +public class DeforestationManager { + final static int RECORD_EXPIRED_SECONDS = 60*5; // 5 min + final static int RECORD_MAX_TIMES = 3; // max number of wood + final static int RECORD_MAX_TIMES_OTHER_HIT_TREE = 10; // if hit 10 times other trees, reset wood + + @Transient private final Player player; + @Transient private final ArrayList currentRecord; + @Transient private final static HashMap ColliderTypeToWoodItemID = new HashMap<>(); + static { + /* define wood types which reflected to item id*/ + ColliderTypeToWoodItemID.put(1,101301); + ColliderTypeToWoodItemID.put(2,101302); + ColliderTypeToWoodItemID.put(3,101303); + ColliderTypeToWoodItemID.put(4,101304); + ColliderTypeToWoodItemID.put(5,101305); + ColliderTypeToWoodItemID.put(6,101306); + ColliderTypeToWoodItemID.put(7,101307); + ColliderTypeToWoodItemID.put(8,101308); + ColliderTypeToWoodItemID.put(9,101309); + ColliderTypeToWoodItemID.put(10,101310); + ColliderTypeToWoodItemID.put(11,101311); + ColliderTypeToWoodItemID.put(12,101312); + } + public DeforestationManager(Player player){ + this.player = player; + this.currentRecord = new ArrayList<>(); + } + public void resetWood(){ + synchronized (currentRecord) { + currentRecord.clear(); + } + } + public void onDeforestationInvoke(HitTreeNotifyOuterClass.HitTreeNotify hit){ + synchronized (currentRecord) { + //Grasscutter.getLogger().info("onDeforestationInvoke! Wood records {}", currentRecord); + VectorOuterClass.Vector hitPosition = hit.getHitPostion(); + int woodType = hit.getWoodType(); + if (ColliderTypeToWoodItemID.containsKey(woodType)) {// is a available wood type + Scene scene = player.getScene(); + int itemId = ColliderTypeToWoodItemID.get(woodType); + int positionHash = hitPosition.hashCode(); + HitTreeRecord record = searchRecord(positionHash); + if (record == null) { + record = new HitTreeRecord(positionHash); + }else{ + currentRecord.remove(record);// move it to last position + } + currentRecord.add(record); + if(currentRecord.size()>RECORD_MAX_TIMES_OTHER_HIT_TREE){ + currentRecord.remove(0); + } + if(record.record()) { + EntityItem entity = new EntityItem(scene, + null, + GameData.getItemDataMap().get(itemId), + new Position(hitPosition.getX(), hitPosition.getY(), hitPosition.getZ()), + 1, + false); + scene.addEntity(entity); + } + //record.record()=false : too many wood they have deforested, no more wood dropped! + } else { + Grasscutter.getLogger().warn("No wood type {} found.", woodType); + } + } + // unknown wood type + } + private HitTreeRecord searchRecord(int id){ + for (HitTreeRecord record : currentRecord) { + if (record.getUnique() == id) { + return record; + } + } + return null; + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/DeforestationManager/HitTreeRecord.java b/src/main/java/emu/grasscutter/game/managers/deforestation/HitTreeRecord.java similarity index 91% rename from src/main/java/emu/grasscutter/game/managers/DeforestationManager/HitTreeRecord.java rename to src/main/java/emu/grasscutter/game/managers/deforestation/HitTreeRecord.java index ee78c7506..e2341f10a 100644 --- a/src/main/java/emu/grasscutter/game/managers/DeforestationManager/HitTreeRecord.java +++ b/src/main/java/emu/grasscutter/game/managers/deforestation/HitTreeRecord.java @@ -1,57 +1,57 @@ -package emu.grasscutter.game.managers.DeforestationManager; - - - -public class HitTreeRecord { - private final int unique; - private short count; // hit this tree times - private long time; // last available hitting time - HitTreeRecord(int unique){ - this.count = 0; - this.time = 0; - this.unique = unique; - } - - /** - * reset hit time - */ - private void resetTime(){ - this.time = System.currentTimeMillis(); - } - - - /** - * commit hit behavior - */ - public boolean record(){ - if (this.count < DeforestationManager.RECORD_MAX_TIMES) { - this.count++; - resetTime(); - return true; - } - // check expired - boolean isWaiting = System.currentTimeMillis() - this.time < DeforestationManager.RECORD_EXPIRED_SECONDS * 1000L; - if(isWaiting){ - return false; - }else{ - this.count = 1; - resetTime(); - return true; - } - } - /** - * get unique id - */ - public int getUnique(){ - return unique; - } - - @Override - public String toString() { - return "HitTreeRecord{" + - "unique=" + unique + - ", count=" + count + - ", time=" + time + - '}'; - } -} +package emu.grasscutter.game.managers.deforestation; + + + +public class HitTreeRecord { + private final int unique; + private short count; // hit this tree times + private long time; // last available hitting time + HitTreeRecord(int unique){ + this.count = 0; + this.time = 0; + this.unique = unique; + } + + /** + * reset hit time + */ + private void resetTime(){ + this.time = System.currentTimeMillis(); + } + + + /** + * commit hit behavior + */ + public boolean record(){ + if (this.count < DeforestationManager.RECORD_MAX_TIMES) { + this.count++; + resetTime(); + return true; + } + // check expired + boolean isWaiting = System.currentTimeMillis() - this.time < DeforestationManager.RECORD_EXPIRED_SECONDS * 1000L; + if(isWaiting){ + return false; + }else{ + this.count = 1; + resetTime(); + return true; + } + } + /** + * get unique id + */ + public int getUnique(){ + return unique; + } + + @Override + public String toString() { + return "HitTreeRecord{" + + "unique=" + unique + + ", count=" + count + + ", time=" + time + + '}'; + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyDropEntry.java b/src/main/java/emu/grasscutter/game/managers/energy/EnergyDropEntry.java similarity index 83% rename from src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyDropEntry.java rename to src/main/java/emu/grasscutter/game/managers/energy/EnergyDropEntry.java index a26140521..6832294a0 100644 --- a/src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyDropEntry.java +++ b/src/main/java/emu/grasscutter/game/managers/energy/EnergyDropEntry.java @@ -1,4 +1,4 @@ -package emu.grasscutter.game.managers.EnergyManager; +package emu.grasscutter.game.managers.energy; import java.util.List; diff --git a/src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyDropInfo.java b/src/main/java/emu/grasscutter/game/managers/energy/EnergyDropInfo.java similarity index 79% rename from src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyDropInfo.java rename to src/main/java/emu/grasscutter/game/managers/energy/EnergyDropInfo.java index 40145c545..2ba4d5842 100644 --- a/src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyDropInfo.java +++ b/src/main/java/emu/grasscutter/game/managers/energy/EnergyDropInfo.java @@ -1,4 +1,4 @@ -package emu.grasscutter.game.managers.EnergyManager; +package emu.grasscutter.game.managers.energy; public class EnergyDropInfo { private int ballId; diff --git a/src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyManager.java b/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java similarity index 99% rename from src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyManager.java rename to src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java index 8efd522d4..82b075228 100644 --- a/src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyManager.java +++ b/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java @@ -1,4 +1,4 @@ -package emu.grasscutter.game.managers.EnergyManager; +package emu.grasscutter.game.managers.energy; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.DataLoader; diff --git a/src/main/java/emu/grasscutter/game/managers/EnergyManager/SkillParticleGenerationEntry.java b/src/main/java/emu/grasscutter/game/managers/energy/SkillParticleGenerationEntry.java similarity index 84% rename from src/main/java/emu/grasscutter/game/managers/EnergyManager/SkillParticleGenerationEntry.java rename to src/main/java/emu/grasscutter/game/managers/energy/SkillParticleGenerationEntry.java index e99f99231..2a65fbddd 100644 --- a/src/main/java/emu/grasscutter/game/managers/EnergyManager/SkillParticleGenerationEntry.java +++ b/src/main/java/emu/grasscutter/game/managers/energy/SkillParticleGenerationEntry.java @@ -1,4 +1,4 @@ -package emu.grasscutter.game.managers.EnergyManager; +package emu.grasscutter.game.managers.energy; import java.util.List; diff --git a/src/main/java/emu/grasscutter/game/managers/EnergyManager/SkillParticleGenerationInfo.java b/src/main/java/emu/grasscutter/game/managers/energy/SkillParticleGenerationInfo.java similarity index 78% rename from src/main/java/emu/grasscutter/game/managers/EnergyManager/SkillParticleGenerationInfo.java rename to src/main/java/emu/grasscutter/game/managers/energy/SkillParticleGenerationInfo.java index a461b1035..6513c98f8 100644 --- a/src/main/java/emu/grasscutter/game/managers/EnergyManager/SkillParticleGenerationInfo.java +++ b/src/main/java/emu/grasscutter/game/managers/energy/SkillParticleGenerationInfo.java @@ -1,4 +1,4 @@ -package emu.grasscutter.game.managers.EnergyManager; +package emu.grasscutter.game.managers.energy; public class SkillParticleGenerationInfo { private int value; diff --git a/src/main/java/emu/grasscutter/game/managers/ForgingManager/ActiveForgeData.java b/src/main/java/emu/grasscutter/game/managers/forging/ActiveForgeData.java similarity index 97% rename from src/main/java/emu/grasscutter/game/managers/ForgingManager/ActiveForgeData.java rename to src/main/java/emu/grasscutter/game/managers/forging/ActiveForgeData.java index 7d11d1985..3820c8685 100644 --- a/src/main/java/emu/grasscutter/game/managers/ForgingManager/ActiveForgeData.java +++ b/src/main/java/emu/grasscutter/game/managers/forging/ActiveForgeData.java @@ -1,4 +1,4 @@ -package emu.grasscutter.game.managers.ForgingManager; +package emu.grasscutter.game.managers.forging; import dev.morphia.annotations.Entity; import emu.grasscutter.utils.Utils; diff --git a/src/main/java/emu/grasscutter/game/managers/ForgingManager/ForgingManager.java b/src/main/java/emu/grasscutter/game/managers/forging/ForgingManager.java similarity index 99% rename from src/main/java/emu/grasscutter/game/managers/ForgingManager/ForgingManager.java rename to src/main/java/emu/grasscutter/game/managers/forging/ForgingManager.java index 7f82f869f..b9908e248 100644 --- a/src/main/java/emu/grasscutter/game/managers/ForgingManager/ForgingManager.java +++ b/src/main/java/emu/grasscutter/game/managers/forging/ForgingManager.java @@ -1,4 +1,4 @@ -package emu.grasscutter.game.managers.ForgingManager; +package emu.grasscutter.game.managers.forging; import java.util.ArrayList; import java.util.HashMap; diff --git a/src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMark.java b/src/main/java/emu/grasscutter/game/managers/mapmark/MapMark.java similarity index 94% rename from src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMark.java rename to src/main/java/emu/grasscutter/game/managers/mapmark/MapMark.java index e49d0c802..59d4f1b20 100644 --- a/src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMark.java +++ b/src/main/java/emu/grasscutter/game/managers/mapmark/MapMark.java @@ -1,66 +1,66 @@ -package emu.grasscutter.game.managers.MapMarkManager; - -import dev.morphia.annotations.Entity; -import emu.grasscutter.net.proto.MapMarkFromTypeOuterClass.MapMarkFromType; -import emu.grasscutter.net.proto.MapMarkPointOuterClass.MapMarkPoint; -import emu.grasscutter.net.proto.MapMarkPointTypeOuterClass.MapMarkPointType; -import emu.grasscutter.utils.Position; - -@Entity -public class MapMark { - private int sceneId; - private String name; - private Position position; - private MapMarkPointType mapMarkPointType; - private int monsterId; - private MapMarkFromType mapMarkFromType; - private int questId; - - @Deprecated // Morhpia - public MapMark() { - this.mapMarkPointType = MapMarkPointType.MAP_MARK_POINT_TYPE_MONSTER; - this.mapMarkFromType = MapMarkFromType.MAP_MARK_FROM_TYPE_MONSTER; - } - - public MapMark(MapMarkPoint mapMarkPoint) { - this.sceneId = mapMarkPoint.getSceneId(); - this.name = mapMarkPoint.getName(); - this.position = new Position( - mapMarkPoint.getPos().getX(), - mapMarkPoint.getPos().getY(), - mapMarkPoint.getPos().getZ() - ); - this.mapMarkPointType = mapMarkPoint.getPointType(); - this.monsterId = mapMarkPoint.getMonsterId(); - this.mapMarkFromType = mapMarkPoint.getFromType(); - this.questId = mapMarkPoint.getQuestId(); - } - - public int getSceneId() { - return this.sceneId; - } - - public String getName() { - return this.name; - } - - public Position getPosition() { - return this.position; - } - - public MapMarkPointType getMapMarkPointType() { - return this.mapMarkPointType; - } - - public int getMonsterId() { - return this.monsterId; - } - - public MapMarkFromType getMapMarkFromType() { - return this.mapMarkFromType; - } - - public int getQuestId() { - return this.questId; - } +package emu.grasscutter.game.managers.mapmark; + +import dev.morphia.annotations.Entity; +import emu.grasscutter.net.proto.MapMarkFromTypeOuterClass.MapMarkFromType; +import emu.grasscutter.net.proto.MapMarkPointOuterClass.MapMarkPoint; +import emu.grasscutter.net.proto.MapMarkPointTypeOuterClass.MapMarkPointType; +import emu.grasscutter.utils.Position; + +@Entity +public class MapMark { + private int sceneId; + private String name; + private Position position; + private MapMarkPointType mapMarkPointType; + private int monsterId; + private MapMarkFromType mapMarkFromType; + private int questId; + + @Deprecated // Morhpia + public MapMark() { + this.mapMarkPointType = MapMarkPointType.MAP_MARK_POINT_TYPE_MONSTER; + this.mapMarkFromType = MapMarkFromType.MAP_MARK_FROM_TYPE_MONSTER; + } + + public MapMark(MapMarkPoint mapMarkPoint) { + this.sceneId = mapMarkPoint.getSceneId(); + this.name = mapMarkPoint.getName(); + this.position = new Position( + mapMarkPoint.getPos().getX(), + mapMarkPoint.getPos().getY(), + mapMarkPoint.getPos().getZ() + ); + this.mapMarkPointType = mapMarkPoint.getPointType(); + this.monsterId = mapMarkPoint.getMonsterId(); + this.mapMarkFromType = mapMarkPoint.getFromType(); + this.questId = mapMarkPoint.getQuestId(); + } + + public int getSceneId() { + return this.sceneId; + } + + public String getName() { + return this.name; + } + + public Position getPosition() { + return this.position; + } + + public MapMarkPointType getMapMarkPointType() { + return this.mapMarkPointType; + } + + public int getMonsterId() { + return this.monsterId; + } + + public MapMarkFromType getMapMarkFromType() { + return this.mapMarkFromType; + } + + public int getQuestId() { + return this.questId; + } } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMarksManager.java b/src/main/java/emu/grasscutter/game/managers/mapmark/MapMarksManager.java similarity index 95% rename from src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMarksManager.java rename to src/main/java/emu/grasscutter/game/managers/mapmark/MapMarksManager.java index 949786d12..1fa36ec58 100644 --- a/src/main/java/emu/grasscutter/game/managers/MapMarkManager/MapMarksManager.java +++ b/src/main/java/emu/grasscutter/game/managers/mapmark/MapMarksManager.java @@ -1,90 +1,90 @@ -package emu.grasscutter.game.managers.MapMarkManager; - -import emu.grasscutter.game.player.Player; -import emu.grasscutter.net.proto.MapMarkPointTypeOuterClass.MapMarkPointType; -import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq; -import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq.Operation; -import emu.grasscutter.server.packet.send.PacketMarkMapRsp; -import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify; -import emu.grasscutter.utils.Position; - -import java.util.HashMap; - -public class MapMarksManager { - public static final int mapMarkMaxCount = 150; - private HashMap mapMarks; - private final Player player; - - public MapMarksManager(Player player) { - this.player = player; - this.mapMarks = player.getMapMarks(); - if (this.mapMarks == null) { this.mapMarks = new HashMap<>(); } - } - - public void handleMapMarkReq(MarkMapReq req) { - Operation op = req.getOp(); - switch (op) { - case OPERATION_ADD -> { - MapMark createMark = new MapMark(req.getMark()); - // keep teleporting functionality on fishhook mark. - if (createMark.getMapMarkPointType() == MapMarkPointType.MAP_MARK_POINT_TYPE_FISH_POOL) { - teleport(player, createMark); - return; - } - addMapMark(createMark); - } - case OPERATION_MOD -> { - MapMark oldMark = new MapMark(req.getOld()); - removeMapMark(oldMark.getPosition()); - MapMark newMark = new MapMark(req.getMark()); - addMapMark(newMark); - } - case OPERATION_DEL -> { - MapMark deleteMark = new MapMark(req.getMark()); - removeMapMark(deleteMark.getPosition()); - } - } - if (op != Operation.OPERATION_GET) { - saveMapMarks(); - } - player.getSession().send(new PacketMarkMapRsp(getMapMarks())); - } - - public HashMap getMapMarks() { - return mapMarks; - } - - public String getMapMarkKey(Position position) { - return "x" + (int)position.getX()+ "z" + (int)position.getZ(); - } - - public void removeMapMark(Position position) { - mapMarks.remove(getMapMarkKey(position)); - } - - public void addMapMark(MapMark mapMark) { - if (mapMarks.size() < mapMarkMaxCount) { - mapMarks.put(getMapMarkKey(mapMark.getPosition()), mapMark); - } - } - - private void saveMapMarks() { - player.setMapMarks(mapMarks); - player.save(); - } - - private void teleport(Player player, MapMark mapMark) { - float y; - try { - y = (float)Integer.parseInt(mapMark.getName()); - } catch (Exception e) { - y = 300; - } - Position pos = mapMark.getPosition(); - player.getPos().set(pos.getX(), y, pos.getZ()); - if (mapMark.getSceneId() != player.getSceneId()) { - player.getWorld().transferPlayerToScene(player, mapMark.getSceneId(), player.getPos()); - } - player.getScene().broadcastPacket(new PacketSceneEntityAppearNotify(player)); - } -} +package emu.grasscutter.game.managers.mapmark; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.proto.MapMarkPointTypeOuterClass.MapMarkPointType; +import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq; +import emu.grasscutter.net.proto.MarkMapReqOuterClass.MarkMapReq.Operation; +import emu.grasscutter.server.packet.send.PacketMarkMapRsp; +import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify; +import emu.grasscutter.utils.Position; + +import java.util.HashMap; + +public class MapMarksManager { + public static final int mapMarkMaxCount = 150; + private HashMap mapMarks; + private final Player player; + + public MapMarksManager(Player player) { + this.player = player; + this.mapMarks = player.getMapMarks(); + if (this.mapMarks == null) { this.mapMarks = new HashMap<>(); } + } + + public void handleMapMarkReq(MarkMapReq req) { + Operation op = req.getOp(); + switch (op) { + case OPERATION_ADD -> { + MapMark createMark = new MapMark(req.getMark()); + // keep teleporting functionality on fishhook mark. + if (createMark.getMapMarkPointType() == MapMarkPointType.MAP_MARK_POINT_TYPE_FISH_POOL) { + teleport(player, createMark); + return; + } + addMapMark(createMark); + } + case OPERATION_MOD -> { + MapMark oldMark = new MapMark(req.getOld()); + removeMapMark(oldMark.getPosition()); + MapMark newMark = new MapMark(req.getMark()); + addMapMark(newMark); + } + case OPERATION_DEL -> { + MapMark deleteMark = new MapMark(req.getMark()); + removeMapMark(deleteMark.getPosition()); + } + } + if (op != Operation.OPERATION_GET) { + saveMapMarks(); + } + player.getSession().send(new PacketMarkMapRsp(getMapMarks())); + } + + public HashMap getMapMarks() { + return mapMarks; + } + + public String getMapMarkKey(Position position) { + return "x" + (int)position.getX()+ "z" + (int)position.getZ(); + } + + public void removeMapMark(Position position) { + mapMarks.remove(getMapMarkKey(position)); + } + + public void addMapMark(MapMark mapMark) { + if (mapMarks.size() < mapMarkMaxCount) { + mapMarks.put(getMapMarkKey(mapMark.getPosition()), mapMark); + } + } + + private void saveMapMarks() { + player.setMapMarks(mapMarks); + player.save(); + } + + private void teleport(Player player, MapMark mapMark) { + float y; + try { + y = (float)Integer.parseInt(mapMark.getName()); + } catch (Exception e) { + y = 300; + } + Position pos = mapMark.getPosition(); + player.getPos().set(pos.getX(), y, pos.getZ()); + if (mapMark.getSceneId() != player.getSceneId()) { + player.getWorld().transferPlayerToScene(player, mapMark.getSceneId(), player.getPos()); + } + player.getScene().broadcastPacket(new PacketSceneEntityAppearNotify(player)); + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java b/src/main/java/emu/grasscutter/game/managers/stamina/AfterUpdateStaminaListener.java similarity index 86% rename from src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java rename to src/main/java/emu/grasscutter/game/managers/stamina/AfterUpdateStaminaListener.java index 11a5c9178..d6e1d5e51 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java +++ b/src/main/java/emu/grasscutter/game/managers/stamina/AfterUpdateStaminaListener.java @@ -1,12 +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, boolean isCharacterStamina); -} +package emu.grasscutter.game.managers.stamina; + +public interface AfterUpdateStaminaListener { + /** + * onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina. + * This gives listeners a chance to intercept this update. + * + * @param reason Why updating stamina. + * @param newStamina New Stamina value. + */ + void onAfterUpdateStamina(String reason, int newStamina, boolean isCharacterStamina); +} diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java b/src/main/java/emu/grasscutter/game/managers/stamina/BeforeUpdateStaminaListener.java similarity index 93% rename from src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java rename to src/main/java/emu/grasscutter/game/managers/stamina/BeforeUpdateStaminaListener.java index 39075f35b..0d47dcee2 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java +++ b/src/main/java/emu/grasscutter/game/managers/stamina/BeforeUpdateStaminaListener.java @@ -1,20 +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, boolean isCharacterStamina); - /** - * onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina. - * This gives listeners a chance to intercept this update. - * @param reason Why updating stamina. - * @param consumption ConsumptionType and RELATIVE stamina change amount. - * @return true if you want to cancel this update, otherwise false. - */ - Consumption onBeforeUpdateStamina(String reason, Consumption consumption, boolean isCharacterStamina); +package emu.grasscutter.game.managers.stamina; + +public interface BeforeUpdateStaminaListener { + /** + * onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina. + * This gives listeners a chance to intercept this update. + * @param reason Why updating stamina. + * @param newStamina New ABSOLUTE stamina value. + * @return true if you want to cancel this update, otherwise false. + */ + int onBeforeUpdateStamina(String reason, int newStamina, boolean isCharacterStamina); + /** + * onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina. + * This gives listeners a chance to intercept this update. + * @param reason Why updating stamina. + * @param consumption ConsumptionType and RELATIVE stamina change amount. + * @return true if you want to cancel this update, otherwise false. + */ + Consumption onBeforeUpdateStamina(String reason, Consumption consumption, boolean isCharacterStamina); } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java b/src/main/java/emu/grasscutter/game/managers/stamina/Consumption.java similarity index 83% rename from src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java rename to src/main/java/emu/grasscutter/game/managers/stamina/Consumption.java index a6185f063..15d777fc6 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java +++ b/src/main/java/emu/grasscutter/game/managers/stamina/Consumption.java @@ -1,18 +1,18 @@ -package emu.grasscutter.game.managers.StaminaManager; - -public class Consumption { - public ConsumptionType type = ConsumptionType.None; - public int amount = 0; - - public Consumption(ConsumptionType type, int amount) { - this.type = type; - this.amount = amount; - } - - public Consumption(ConsumptionType type) { - this(type, type.amount); - } - - public Consumption() { - } -} +package emu.grasscutter.game.managers.stamina; + +public class Consumption { + public ConsumptionType type = ConsumptionType.None; + public int amount = 0; + + public Consumption(ConsumptionType type, int amount) { + this.type = type; + this.amount = amount; + } + + public Consumption(ConsumptionType type) { + this(type, type.amount); + } + + public Consumption() { + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java b/src/main/java/emu/grasscutter/game/managers/stamina/ConsumptionType.java similarity index 90% rename from src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java rename to src/main/java/emu/grasscutter/game/managers/stamina/ConsumptionType.java index 506bf1728..ddf21b166 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java +++ b/src/main/java/emu/grasscutter/game/managers/stamina/ConsumptionType.java @@ -1,37 +1,37 @@ -package emu.grasscutter.game.managers.StaminaManager; - -public enum ConsumptionType { - None(0), - - // consume - CLIMBING(-150), - CLIMB_START(-500), - CLIMB_JUMP(-2500), - DASH(-360), - FIGHT(0), // See StaminaManager.getFightConsumption() - FLY(-60), - // Slow swimming is handled per movement, not per second. - // Arm movement frequency depends on gender/age/height. - // TODO: Instead of cost -80 per tick, find a proper way to calculate cost. - SKIFF_DASH(-204), - SPRINT(-1800), - SWIM_DASH_START(-2000), - SWIM_DASH(-204), // -10.2 per second, 5Hz = -204 each tick - SWIMMING(-80), - TALENT_DASH(-300), // -1500 per second, 5Hz = -300 each tick - TALENT_DASH_START(-1000), - - // restore - POWERED_FLY(500), - POWERED_SKIFF(500), - RUN(500), - SKIFF(500), - STANDBY(500), - WALK(500); - - public final int amount; - - ConsumptionType(int amount) { - this.amount = amount; - } +package emu.grasscutter.game.managers.stamina; + +public enum ConsumptionType { + None(0), + + // consume + CLIMBING(-150), + CLIMB_START(-500), + CLIMB_JUMP(-2500), + DASH(-360), + FIGHT(0), // See StaminaManager.getFightConsumption() + FLY(-60), + // Slow swimming is handled per movement, not per second. + // Arm movement frequency depends on gender/age/height. + // TODO: Instead of cost -80 per tick, find a proper way to calculate cost. + SKIFF_DASH(-204), + SPRINT(-1800), + SWIM_DASH_START(-2000), + SWIM_DASH(-204), // -10.2 per second, 5Hz = -204 each tick + SWIMMING(-80), + TALENT_DASH(-300), // -1500 per second, 5Hz = -300 each tick + TALENT_DASH_START(-1000), + + // restore + POWERED_FLY(500), + POWERED_SKIFF(500), + RUN(500), + SKIFF(500), + STANDBY(500), + WALK(500); + + public final int amount; + + ConsumptionType(int amount) { + this.amount = amount; + } } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/README.md b/src/main/java/emu/grasscutter/game/managers/stamina/README.md similarity index 96% rename from src/main/java/emu/grasscutter/game/managers/StaminaManager/README.md rename to src/main/java/emu/grasscutter/game/managers/stamina/README.md index 39a4e7988..87f88b151 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/README.md +++ b/src/main/java/emu/grasscutter/game/managers/stamina/README.md @@ -1,73 +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) { - // ... - } -} +# 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/stamina/StaminaManager.java similarity index 97% rename from src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java rename to src/main/java/emu/grasscutter/game/managers/stamina/StaminaManager.java index 4d69e7a99..85c8fb7b3 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/stamina/StaminaManager.java @@ -1,717 +1,717 @@ -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.avatar.Avatar; -import emu.grasscutter.game.entity.EntityAvatar; -import emu.grasscutter.game.entity.GameEntity; -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.game.props.WeaponType; -import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo; -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.Vector; -import emu.grasscutter.net.proto.VehicleInteractTypeOuterClass.VehicleInteractType; -import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.*; -import emu.grasscutter.utils.Position; -import org.jetbrains.annotations.NotNull; - -import java.util.*; - -import static emu.grasscutter.Configuration.GAME_OPTIONS; - -public class StaminaManager { - - // TODO: Skiff state detection? - private final Player player; - private static final HashMap> MotionStatesCategorized = new HashMap<>() {{ - put("CLIMB", new HashSet<>(List.of( - MotionState.MOTION_STATE_CLIMB, // sustained, when not moving no cost no recover - MotionState.MOTION_STATE_STANDBY_TO_CLIMB // NOT OBSERVED, see MOTION_JUMP_UP_WALL_FOR_STANDBY - ))); - put("DASH", new HashSet<>(List.of( - MotionState.MOTION_STATE_DANGER_DASH, // sustained - MotionState.MOTION_STATE_DASH // sustained - ))); - put("FLY", new HashSet<>(List.of( - MotionState.MOTION_STATE_FLY, // sustained - MotionState.MOTION_STATE_FLY_FAST, // sustained - MotionState.MOTION_STATE_FLY_SLOW, // sustained - MotionState.MOTION_STATE_POWERED_FLY // sustained, recover - ))); - put("RUN", new HashSet<>(List.of( - MotionState.MOTION_STATE_DANGER_RUN, // sustained, recover - MotionState.MOTION_STATE_RUN // sustained, recover - ))); - put("SKIFF", new HashSet<>(List.of( - MotionState.MOTION_STATE_SKIFF_BOARDING, // NOT OBSERVED even when boarding - MotionState.MOTION_STATE_SKIFF_DASH, // sustained, observed with waverider entity ID. - MotionState.MOTION_STATE_SKIFF_NORMAL, // sustained, OBSERVED when both normal and dashing - MotionState.MOTION_STATE_SKIFF_POWERED_DASH // sustained, recover - ))); - put("STANDBY", new HashSet<>(List.of( - MotionState.MOTION_STATE_DANGER_STANDBY_MOVE, // sustained, recover - MotionState.MOTION_STATE_DANGER_STANDBY, // sustained, recover - MotionState.MOTION_STATE_LADDER_TO_STANDBY, // NOT OBSERVED - MotionState.MOTION_STATE_STANDBY_MOVE, // sustained, recover - MotionState.MOTION_STATE_STANDBY // sustained, recover - ))); - put("SWIM", new HashSet<>(List.of( - MotionState.MOTION_STATE_SWIM_IDLE, // sustained - MotionState.MOTION_STATE_SWIM_DASH, // immediate and sustained - MotionState.MOTION_STATE_SWIM_JUMP, // NOT OBSERVED - MotionState.MOTION_STATE_SWIM_MOVE // sustained - ))); - put("WALK", new HashSet<>(List.of( - MotionState.MOTION_STATE_DANGER_WALK, // sustained, recover - MotionState.MOTION_STATE_WALK // sustained, recover - ))); - put("OTHER", new HashSet<>(List.of( - MotionState.MOTION_STATE_CLIMB_JUMP, // cost only once if repeated without switching state - MotionState.MOTION_STATE_DASH_BEFORE_SHAKE, // immediate one time sprint charge. - MotionState.MOTION_STATE_FIGHT, // immediate, if sustained then subsequent will be MOTION_NOTIFY - MotionState.MOTION_STATE_JUMP_UP_WALL_FOR_STANDBY, // immediate, observed when RUN/WALK->CLIMB - MotionState.MOTION_STATE_NOTIFY, // can be either cost or recover - check previous state and check skill casting - MotionState.MOTION_STATE_SIT_IDLE, // sustained, recover - MotionState.MOTION_STATE_JUMP // recover - ))); - put("NOCOST_NORECOVER", new HashSet<>(List.of( - MotionState.MOTION_STATE_LADDER_SLIP, // NOT OBSERVED - MotionState.MOTION_STATE_SLIP, // sustained, no cost no recover - MotionState.MOTION_STATE_FLY_IDLE // NOT OBSERVED - ))); - put("IGNORE", new HashSet<>(List.of( - // these states have no impact on stamina - MotionState.MOTION_STATE_CROUCH_IDLE, - MotionState.MOTION_STATE_CROUCH_MOVE, - MotionState.MOTION_STATE_CROUCH_ROLL, - MotionState.MOTION_STATE_DESTROY_VEHICLE, - MotionState.MOTION_STATE_FALL_ON_GROUND, - MotionState.MOTION_STATE_FOLLOW_ROUTE, - MotionState.MOTION_STATE_FORCE_SET_POS, - MotionState.MOTION_STATE_GO_UPSTAIRS, - MotionState.MOTION_STATE_JUMP_OFF_WALL, - MotionState.MOTION_STATE_LADDER_IDLE, - MotionState.MOTION_STATE_LADDER_MOVE, - MotionState.MOTION_STATE_LAND_SPEED, - MotionState.MOTION_STATE_MOVE_FAIL_ACK, - MotionState.MOTION_STATE_NONE, - MotionState.MOTION_STATE_NUM, - MotionState.MOTION_STATE_QUEST_FORCE_DRAG, - MotionState.MOTION_STATE_RESET, - MotionState.MOTION_STATE_STANDBY_TO_LADDER, - MotionState.MOTION_STATE_WATERFALL - ))); - }}; - - private final Logger logger = Grasscutter.getLogger(); - public final static int GlobalCharacterMaximumStamina = 24000; - public final static int GlobalVehicleMaxStamina = 24000; - private Position currentCoordinates = new Position(0, 0, 0); - private Position previousCoordinates = new Position(0, 0, 0); - private MotionState currentState = MotionState.MOTION_STATE_STANDBY; - private MotionState previousState = MotionState.MOTION_STATE_STANDBY; - private Timer sustainedStaminaHandlerTimer; - 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 int vehicleId = -1; - private int vehicleStamina = GlobalVehicleMaxStamina; - private static final HashSet TalentMovements = new HashSet<>(List.of( - 10013, 10413 - )); - private static final HashMap ClimbFoodReductionMap = new HashMap<>() {{ - // TODO: get real food id - put(0, 0.8f); // Sample food - }}; - private static final HashMap DashFoodReductionMap = new HashMap<>() {{ - // TODO: get real food id - put(0, 0.8f); // Sample food - }}; - private static final HashMap FlyFoodReductionMap = new HashMap<>() {{ - // TODO: get real food id - put(0, 0.8f); // Sample food - }}; - private static final HashMap SwimFoodReductionMap = new HashMap<>() {{ - // TODO: get real food id - put(0, 0.8f); // Sample food - }}; - private static final HashMap ClimbTalentReductionMap = new HashMap<>() {{ - put(262301, 0.8f); - }}; - private static final HashMap FlyTalentReductionMap = new HashMap<>() {{ - put(212301, 0.8f); - put(222301, 0.8f); - }}; - private static final HashMap SwimTalentReductionMap = new HashMap<>() {{ - put(242301, 0.8f); - put(542301, 0.8f); - }}; - - public static void initialize() { - // TODO: Initialize foods etc. - } - - public StaminaManager(Player player) { - this.player = player; - } - - // Accessors - - public void setSkillCast(int skillId, int skillCasterId) { - lastSkillFirstTick = true; - lastSkillId = skillId; - lastSkillCasterId = skillCasterId; - } - - public int getMaxCharacterStamina() { - return player.getProperty(PlayerProperty.PROP_MAX_STAMINA); - } - - public int getCurrentCharacterStamina() { - return player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); - } - - public int getMaxVehicleStamina() { - return GlobalVehicleMaxStamina; - } - - public int getCurrentVehicleStamina() { - return vehicleStamina; - } - - 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(); - logger.trace("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates + - ", " + diffX + ", " + diffY + ", " + diffZ); - return Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.2 || Math.abs(diffZ) > 0.3; - } - - public int updateStaminaRelative(GameSession session, Consumption consumption, boolean isCharacterStamina) { - int currentStamina = isCharacterStamina ? getCurrentCharacterStamina() : getCurrentVehicleStamina(); - if (consumption.amount == 0) { - return currentStamina; - } - // notify will update - for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { - Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.type.toString(), consumption, isCharacterStamina); - if ((overriddenConsumption.type != consumption.type) && (overriddenConsumption.amount != consumption.amount)) { - logger.debug("Stamina update relative(" + - consumption.type.toString() + ", " + consumption.amount + ") overridden to relative(" + - consumption.type.toString() + ", " + consumption.amount + ") by: " + listener.getKey()); - return currentStamina; - } - } - int maxStamina = isCharacterStamina ? getMaxCharacterStamina() : getMaxVehicleStamina(); - logger.trace((isCharacterStamina ? "C " : "V ") + currentStamina + "/" + maxStamina + "\t" + currentState + "\t" + - (isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.type + "," + - consumption.amount + ")"); - int newStamina = currentStamina + consumption.amount; - if (newStamina < 0) { - newStamina = 0; - } else if (newStamina > maxStamina) { - newStamina = maxStamina; - } - return setStamina(session, consumption.type.toString(), newStamina, isCharacterStamina); - } - - public int updateStaminaAbsolute(GameSession session, String reason, int newStamina, boolean isCharacterStamina) { - int currentStamina = isCharacterStamina ? getCurrentCharacterStamina() : getCurrentVehicleStamina(); - // notify will update - for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { - int overriddenNewStamina = listener.getValue().onBeforeUpdateStamina(reason, newStamina, isCharacterStamina); - if (overriddenNewStamina != newStamina) { - logger.debug("Stamina update absolute(" + - reason + ", " + newStamina + ") overridden to absolute(" + - reason + ", " + newStamina + ") by: " + listener.getKey()); - return currentStamina; - } - } - int maxStamina = isCharacterStamina ? getMaxCharacterStamina() : getMaxVehicleStamina(); - if (newStamina < 0) { - newStamina = 0; - } else if (newStamina > maxStamina) { - newStamina = maxStamina; - } - return setStamina(session, reason, newStamina, isCharacterStamina); - } - - // Returns new stamina and sends PlayerPropNotify or VehicleStaminaNotify - public int setStamina(GameSession session, String reason, int newStamina, boolean isCharacterStamina) { - // 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); - session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); - } else { - vehicleStamina = newStamina; - session.send(new PacketVehicleStaminaNotify(vehicleId, ((float) newStamina) / 100)); - } - // notify updated - for (Map.Entry listener : afterUpdateStaminaListeners.entrySet()) { - listener.getValue().onAfterUpdateStamina(reason, newStamina, isCharacterStamina); - } - return newStamina; - } - - // 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 (!player.isPaused() && sustainedStaminaHandlerTimer == null) { - sustainedStaminaHandlerTimer = new Timer(); - sustainedStaminaHandlerTimer.scheduleAtFixedRate(new SustainedStaminaHandler(), 0, 200); - logger.debug("[MovementManager] SustainedStaminaHandlerTimer started"); - } - } - - public void stopSustainedStaminaHandler() { - if (sustainedStaminaHandlerTimer != null) { - sustainedStaminaHandlerTimer.cancel(); - sustainedStaminaHandlerTimer = null; - logger.debug("[MovementManager] SustainedStaminaHandlerTimer stopped"); - } - } - - // Handlers - - // External trigger handler - - public void handleEvtDoSkillSuccNotify(GameSession session, int skillId, int casterId) { - // Ignore if skill not cast by not current active avatar - if (casterId != player.getTeamManager().getCurrentAvatarEntity().getId()) { - return; - } - setSkillCast(skillId, casterId); - // Handle immediate stamina cost - Avatar currentAvatar = player.getTeamManager().getCurrentAvatarEntity().getAvatar(); - if (currentAvatar.getAvatarData().getWeaponType() == WeaponType.WEAPON_CLAYMORE) { - // 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 (BowAvatars.contains(currentAvatarId) - || SwordAvatars.contains(currentAvatarId) - || PolearmAvatars.contains(currentAvatarId) - || CatalystAvatars.contains(currentAvatarId) - ) { - return; - } - */ - //handleImmediateStamina(session, skillId); - } - - public void handleMixinCostStamina(boolean isSwim) { - // Talent moving and claymore avatar charged attack duration - // logger.trace("abilityMixinCostStamina: isSwim: " + isSwim + "\tlastSkill: " + lastSkillId); - 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; - MotionInfo motionInfo = moveInfo.getMotionInfo(); - MotionState motionState = motionInfo.getState(); - int notifyEntityId = entity.getId(); - int currentAvatarEntityId = session.getPlayer().getTeamManager().getCurrentAvatarEntity().getId(); - if (notifyEntityId != currentAvatarEntityId && notifyEntityId != vehicleId) { - return; - } - currentState = motionState; - // logger.trace(currentState + "\t" + (notifyEntityId == currentAvatarEntityId ? "character" : "vehicle")); - 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, motionState); - } - - public void handleVehicleInteractReq(GameSession session, int vehicleId, VehicleInteractType vehicleInteractType) { - if (vehicleInteractType == VehicleInteractType.VEHICLE_INTERACT_TYPE_IN) { - this.vehicleId = vehicleId; - // Reset character stamina here to prevent falling into water immediately on ejection if char stamina is - // close to empty when boarding. - updateStaminaAbsolute(session, "board vehicle", getMaxCharacterStamina(), true); - updateStaminaAbsolute(session, "board vehicle", getMaxVehicleStamina(), false); - } else { - this.vehicleId = -1; - } - } - - // Internal handler - - private void handleImmediateStamina(GameSession session, @NotNull MotionState motionState) { - switch (motionState) { - case MOTION_STATE_CLIMB: - if (currentState != MotionState.MOTION_STATE_CLIMB) { - updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START), true); - } - break; - case MOTION_STATE_DASH_BEFORE_SHAKE: - if (previousState != MotionState.MOTION_STATE_DASH_BEFORE_SHAKE) { - updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT), true); - } - break; - case MOTION_STATE_CLIMB_JUMP: - if (previousState != MotionState.MOTION_STATE_CLIMB_JUMP) { - updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP), true); - } - break; - case MOTION_STATE_SWIM_DASH: - if (previousState != MotionState.MOTION_STATE_SWIM_DASH) { - updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START), true); - } - break; - } - } - - private void handleImmediateStamina(GameSession session, int skillId) { - Consumption consumption = getFightConsumption(skillId); - updateStaminaRelative(session, consumption, true); - } - - private class SustainedStaminaHandler extends TimerTask { - public void run() { - boolean moving = isPlayerMoving(); - int currentCharacterStamina = getCurrentCharacterStamina(); - int maxCharacterStamina = getMaxCharacterStamina(); - int currentVehicleStamina = getCurrentVehicleStamina(); - int maxVehicleStamina = getMaxVehicleStamina(); - if (moving || (currentCharacterStamina < maxCharacterStamina) || (currentVehicleStamina < maxVehicleStamina)) { - logger.trace("Player moving: " + moving + ", stamina full: " + - (currentCharacterStamina >= maxCharacterStamina) + ", recalculate stamina"); - boolean isCharacterStamina = true; - Consumption consumption; - if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { - consumption = getClimbConsumption(); - } else if (MotionStatesCategorized.get("DASH").contains(currentState)) { - consumption = getDashConsumption(); - } else if (MotionStatesCategorized.get("FLY").contains(currentState)) { - consumption = getFlyConsumption(); - } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { - consumption = new Consumption(ConsumptionType.RUN); - } else if (MotionStatesCategorized.get("SKIFF").contains(currentState)) { - consumption = getSkiffConsumption(); - isCharacterStamina = false; - } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { - 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("NOCOST_NORECOVER").contains(currentState)) { - consumption = new Consumption(); - } else if (MotionStatesCategorized.get("OTHER").contains(currentState)) { - consumption = getOtherConsumptions(); - } else { // ignore - return; - } - - if (consumption.amount < 0 && isCharacterStamina) { - // Do not apply reduction factor when recovering stamina - if (player.getTeamManager().getTeamResonances().contains(10301)) { - consumption.amount *= 0.85f; - } - } - // Delay 1 seconds before starts recovering stamina - if (consumption.amount != 0 && cachedSession != null) { - if (consumption.amount < 0) { - staminaRecoverDelay = 0; - } - if (consumption.amount > 0 - && consumption.type != ConsumptionType.POWERED_FLY - && consumption.type != ConsumptionType.POWERED_SKIFF) { - // For POWERED_* recover immediately - things like Amber's gliding exam and skiff challenges may require this. - if (staminaRecoverDelay < 5) { - // For others recover after 1 seconds (5 ticks) - as official server does. - staminaRecoverDelay++; - consumption.amount = 0; - logger.trace("Delaying recovery: " + staminaRecoverDelay); - } - } - updateStaminaRelative(cachedSession, consumption, isCharacterStamina); - } - } - previousState = currentState; - previousCoordinates = new Position( - currentCoordinates.getX(), - currentCoordinates.getY(), - currentCoordinates.getZ() - ); - } - } - - private void handleDrowning() { - // TODO: fix drowning waverider entity - int stamina = getCurrentCharacterStamina(); - if (stamina < 10) { - logger.trace(getCurrentCharacterStamina() + "/" + - getMaxCharacterStamina() + "\t" + currentState); - if (currentState != MotionState.MOTION_STATE_SWIM_IDLE) { - killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_TYPE_DRAWN); - } - } - } - - // Consumption Calculators - - // Stamina Consumption Reduction: https://genshin-impact.fandom.com/wiki/Stamina - - private Consumption getFightConsumption(int skillCasting) { - // Talent moving - if (TalentMovements.contains(skillCasting)) { - // TODO: recover 1000 if kamisato hits an enemy at the end of dashing - return getTalentMovingSustainedCost(skillCasting); - } - // Bow avatar charged attack - Avatar currentAvatar = player.getTeamManager().getCurrentAvatarEntity().getAvatar(); - - switch (currentAvatar.getAvatarData().getWeaponType()) { - case WEAPON_BOW: - return getBowSustainedCost(skillCasting); - case WEAPON_CLAYMORE: - return getClaymoreSustainedCost(skillCasting); - case WEAPON_CATALYST: - return getCatalystCost(skillCasting); - case WEAPON_POLE: - return getPolearmCost(skillCasting); - case WEAPON_SWORD_ONE_HAND: - return getSwordCost(skillCasting); - } - - return new Consumption(); - } - - private Consumption getClimbConsumption() { - Consumption consumption = new Consumption(); - if (currentState == MotionState.MOTION_STATE_CLIMB && isPlayerMoving()) { - consumption.type = ConsumptionType.CLIMBING; - consumption.amount = ConsumptionType.CLIMBING.amount; - } - // Climbing specific reductions - consumption.amount *= getFoodCostReductionFactor(ClimbFoodReductionMap); - consumption.amount *= getTalentCostReductionFactor(ClimbTalentReductionMap); - return consumption; - } - - private Consumption getSwimConsumptions() { - handleDrowning(); - Consumption consumption = new Consumption(); - if (currentState == MotionState.MOTION_STATE_SWIM_MOVE) { - consumption.type = ConsumptionType.SWIMMING; - consumption.amount = ConsumptionType.SWIMMING.amount; - } - if (currentState == MotionState.MOTION_STATE_SWIM_DASH) { - consumption.type = ConsumptionType.SWIM_DASH; - consumption.amount = ConsumptionType.SWIM_DASH.amount; - } - // Swimming specific reductions - consumption.amount *= getFoodCostReductionFactor(SwimFoodReductionMap); - consumption.amount *= getTalentCostReductionFactor(SwimTalentReductionMap); - return consumption; - } - - private Consumption getDashConsumption() { - Consumption consumption = new Consumption(); - if (currentState == MotionState.MOTION_STATE_DASH) { - consumption.type = ConsumptionType.DASH; - consumption.amount = ConsumptionType.DASH.amount; - // Dashing specific reductions - consumption.amount *= getFoodCostReductionFactor(DashFoodReductionMap); - } - return consumption; - } - - private Consumption getFlyConsumption() { - // POWERED_FLY, e.g. wind tunnel - if (currentState == MotionState.MOTION_STATE_POWERED_FLY) { - return new Consumption(ConsumptionType.POWERED_FLY); - } - Consumption consumption = new Consumption(ConsumptionType.FLY); - // Flying specific reductions - consumption.amount *= getFoodCostReductionFactor(FlyFoodReductionMap); - consumption.amount *= getTalentCostReductionFactor(FlyTalentReductionMap); - return consumption; - } - - private Consumption getSkiffConsumption() { - // No known reduction for skiffing. - return switch (currentState) { - case MOTION_STATE_SKIFF_DASH -> new Consumption(ConsumptionType.SKIFF_DASH); - case MOTION_STATE_SKIFF_POWERED_DASH -> new Consumption(ConsumptionType.POWERED_SKIFF); - case MOTION_STATE_SKIFF_NORMAL -> new Consumption(ConsumptionType.SKIFF); - default -> new Consumption(); - }; - } - - private Consumption getOtherConsumptions() { - switch (currentState) { - case MOTION_STATE_NOTIFY: -// if (BowSkills.contains(lastSkillId)) { -// return new Consumption(ConsumptionType.FIGHT, 500); -// } - break; - case MOTION_STATE_FIGHT: - // TODO: what if charged attack - return new Consumption(ConsumptionType.FIGHT, 500); - } - - 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 (talentReductionMap.containsKey(skillId)) { - float potentialLowerReduction = talentReductionMap.get(skillId); - if (potentialLowerReduction < reduction) { - reduction = potentialLowerReduction; - } - } - } - } - return reduction; - } - - 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; - } - - 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 getCatalystCost(int skillId) { - Consumption consumption = new Consumption(ConsumptionType.FIGHT, -5000); - // Character specific handling - switch (skillId) { - // TODO: - } - return consumption; - } - - private Consumption getClaymoreSustainedCost(int skillId) { - Consumption consumption = new Consumption(ConsumptionType.FIGHT, -1333); // 4000 / 3 = 1333 - // Character specific handling - switch (skillId) { - case 10571: - case 10532: - consumption.amount = 0; - break; - case 10160: - if (player.getTeamManager().getCurrentAvatarEntity().getAvatar().getProudSkillList().contains(162101)) { - consumption.amount /= 2; - } - break; - } - return consumption; - } - - private Consumption getPolearmCost(int skillId) { - Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2500); - // Character specific handling - switch (skillId) { - // TODO: - } - return consumption; - } - - private Consumption getSwordCost(int skillId) { - Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2000); - // Character specific handling - switch (skillId) { - case 10421: - consumption.amount = -2500; - break; - } - return consumption; - } -} +package emu.grasscutter.game.managers.stamina; + +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.avatar.Avatar; +import emu.grasscutter.game.entity.EntityAvatar; +import emu.grasscutter.game.entity.GameEntity; +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.game.props.WeaponType; +import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo; +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.Vector; +import emu.grasscutter.net.proto.VehicleInteractTypeOuterClass.VehicleInteractType; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.*; +import emu.grasscutter.utils.Position; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +import static emu.grasscutter.Configuration.GAME_OPTIONS; + +public class StaminaManager { + + // TODO: Skiff state detection? + private final Player player; + private static final HashMap> MotionStatesCategorized = new HashMap<>() {{ + put("CLIMB", new HashSet<>(List.of( + MotionState.MOTION_STATE_CLIMB, // sustained, when not moving no cost no recover + MotionState.MOTION_STATE_STANDBY_TO_CLIMB // NOT OBSERVED, see MOTION_JUMP_UP_WALL_FOR_STANDBY + ))); + put("DASH", new HashSet<>(List.of( + MotionState.MOTION_STATE_DANGER_DASH, // sustained + MotionState.MOTION_STATE_DASH // sustained + ))); + put("FLY", new HashSet<>(List.of( + MotionState.MOTION_STATE_FLY, // sustained + MotionState.MOTION_STATE_FLY_FAST, // sustained + MotionState.MOTION_STATE_FLY_SLOW, // sustained + MotionState.MOTION_STATE_POWERED_FLY // sustained, recover + ))); + put("RUN", new HashSet<>(List.of( + MotionState.MOTION_STATE_DANGER_RUN, // sustained, recover + MotionState.MOTION_STATE_RUN // sustained, recover + ))); + put("SKIFF", new HashSet<>(List.of( + MotionState.MOTION_STATE_SKIFF_BOARDING, // NOT OBSERVED even when boarding + MotionState.MOTION_STATE_SKIFF_DASH, // sustained, observed with waverider entity ID. + MotionState.MOTION_STATE_SKIFF_NORMAL, // sustained, OBSERVED when both normal and dashing + MotionState.MOTION_STATE_SKIFF_POWERED_DASH // sustained, recover + ))); + put("STANDBY", new HashSet<>(List.of( + MotionState.MOTION_STATE_DANGER_STANDBY_MOVE, // sustained, recover + MotionState.MOTION_STATE_DANGER_STANDBY, // sustained, recover + MotionState.MOTION_STATE_LADDER_TO_STANDBY, // NOT OBSERVED + MotionState.MOTION_STATE_STANDBY_MOVE, // sustained, recover + MotionState.MOTION_STATE_STANDBY // sustained, recover + ))); + put("SWIM", new HashSet<>(List.of( + MotionState.MOTION_STATE_SWIM_IDLE, // sustained + MotionState.MOTION_STATE_SWIM_DASH, // immediate and sustained + MotionState.MOTION_STATE_SWIM_JUMP, // NOT OBSERVED + MotionState.MOTION_STATE_SWIM_MOVE // sustained + ))); + put("WALK", new HashSet<>(List.of( + MotionState.MOTION_STATE_DANGER_WALK, // sustained, recover + MotionState.MOTION_STATE_WALK // sustained, recover + ))); + put("OTHER", new HashSet<>(List.of( + MotionState.MOTION_STATE_CLIMB_JUMP, // cost only once if repeated without switching state + MotionState.MOTION_STATE_DASH_BEFORE_SHAKE, // immediate one time sprint charge. + MotionState.MOTION_STATE_FIGHT, // immediate, if sustained then subsequent will be MOTION_NOTIFY + MotionState.MOTION_STATE_JUMP_UP_WALL_FOR_STANDBY, // immediate, observed when RUN/WALK->CLIMB + MotionState.MOTION_STATE_NOTIFY, // can be either cost or recover - check previous state and check skill casting + MotionState.MOTION_STATE_SIT_IDLE, // sustained, recover + MotionState.MOTION_STATE_JUMP // recover + ))); + put("NOCOST_NORECOVER", new HashSet<>(List.of( + MotionState.MOTION_STATE_LADDER_SLIP, // NOT OBSERVED + MotionState.MOTION_STATE_SLIP, // sustained, no cost no recover + MotionState.MOTION_STATE_FLY_IDLE // NOT OBSERVED + ))); + put("IGNORE", new HashSet<>(List.of( + // these states have no impact on stamina + MotionState.MOTION_STATE_CROUCH_IDLE, + MotionState.MOTION_STATE_CROUCH_MOVE, + MotionState.MOTION_STATE_CROUCH_ROLL, + MotionState.MOTION_STATE_DESTROY_VEHICLE, + MotionState.MOTION_STATE_FALL_ON_GROUND, + MotionState.MOTION_STATE_FOLLOW_ROUTE, + MotionState.MOTION_STATE_FORCE_SET_POS, + MotionState.MOTION_STATE_GO_UPSTAIRS, + MotionState.MOTION_STATE_JUMP_OFF_WALL, + MotionState.MOTION_STATE_LADDER_IDLE, + MotionState.MOTION_STATE_LADDER_MOVE, + MotionState.MOTION_STATE_LAND_SPEED, + MotionState.MOTION_STATE_MOVE_FAIL_ACK, + MotionState.MOTION_STATE_NONE, + MotionState.MOTION_STATE_NUM, + MotionState.MOTION_STATE_QUEST_FORCE_DRAG, + MotionState.MOTION_STATE_RESET, + MotionState.MOTION_STATE_STANDBY_TO_LADDER, + MotionState.MOTION_STATE_WATERFALL + ))); + }}; + + private final Logger logger = Grasscutter.getLogger(); + public final static int GlobalCharacterMaximumStamina = 24000; + public final static int GlobalVehicleMaxStamina = 24000; + private Position currentCoordinates = new Position(0, 0, 0); + private Position previousCoordinates = new Position(0, 0, 0); + private MotionState currentState = MotionState.MOTION_STATE_STANDBY; + private MotionState previousState = MotionState.MOTION_STATE_STANDBY; + private Timer sustainedStaminaHandlerTimer; + 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 int vehicleId = -1; + private int vehicleStamina = GlobalVehicleMaxStamina; + private static final HashSet TalentMovements = new HashSet<>(List.of( + 10013, 10413 + )); + private static final HashMap ClimbFoodReductionMap = new HashMap<>() {{ + // TODO: get real food id + put(0, 0.8f); // Sample food + }}; + private static final HashMap DashFoodReductionMap = new HashMap<>() {{ + // TODO: get real food id + put(0, 0.8f); // Sample food + }}; + private static final HashMap FlyFoodReductionMap = new HashMap<>() {{ + // TODO: get real food id + put(0, 0.8f); // Sample food + }}; + private static final HashMap SwimFoodReductionMap = new HashMap<>() {{ + // TODO: get real food id + put(0, 0.8f); // Sample food + }}; + private static final HashMap ClimbTalentReductionMap = new HashMap<>() {{ + put(262301, 0.8f); + }}; + private static final HashMap FlyTalentReductionMap = new HashMap<>() {{ + put(212301, 0.8f); + put(222301, 0.8f); + }}; + private static final HashMap SwimTalentReductionMap = new HashMap<>() {{ + put(242301, 0.8f); + put(542301, 0.8f); + }}; + + public static void initialize() { + // TODO: Initialize foods etc. + } + + public StaminaManager(Player player) { + this.player = player; + } + + // Accessors + + public void setSkillCast(int skillId, int skillCasterId) { + lastSkillFirstTick = true; + lastSkillId = skillId; + lastSkillCasterId = skillCasterId; + } + + public int getMaxCharacterStamina() { + return player.getProperty(PlayerProperty.PROP_MAX_STAMINA); + } + + public int getCurrentCharacterStamina() { + return player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + } + + public int getMaxVehicleStamina() { + return GlobalVehicleMaxStamina; + } + + public int getCurrentVehicleStamina() { + return vehicleStamina; + } + + 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(); + logger.trace("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates + + ", " + diffX + ", " + diffY + ", " + diffZ); + return Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.2 || Math.abs(diffZ) > 0.3; + } + + public int updateStaminaRelative(GameSession session, Consumption consumption, boolean isCharacterStamina) { + int currentStamina = isCharacterStamina ? getCurrentCharacterStamina() : getCurrentVehicleStamina(); + if (consumption.amount == 0) { + return currentStamina; + } + // notify will update + for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { + Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.type.toString(), consumption, isCharacterStamina); + if ((overriddenConsumption.type != consumption.type) && (overriddenConsumption.amount != consumption.amount)) { + logger.debug("Stamina update relative(" + + consumption.type.toString() + ", " + consumption.amount + ") overridden to relative(" + + consumption.type.toString() + ", " + consumption.amount + ") by: " + listener.getKey()); + return currentStamina; + } + } + int maxStamina = isCharacterStamina ? getMaxCharacterStamina() : getMaxVehicleStamina(); + logger.trace((isCharacterStamina ? "C " : "V ") + currentStamina + "/" + maxStamina + "\t" + currentState + "\t" + + (isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.type + "," + + consumption.amount + ")"); + int newStamina = currentStamina + consumption.amount; + if (newStamina < 0) { + newStamina = 0; + } else if (newStamina > maxStamina) { + newStamina = maxStamina; + } + return setStamina(session, consumption.type.toString(), newStamina, isCharacterStamina); + } + + public int updateStaminaAbsolute(GameSession session, String reason, int newStamina, boolean isCharacterStamina) { + int currentStamina = isCharacterStamina ? getCurrentCharacterStamina() : getCurrentVehicleStamina(); + // notify will update + for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { + int overriddenNewStamina = listener.getValue().onBeforeUpdateStamina(reason, newStamina, isCharacterStamina); + if (overriddenNewStamina != newStamina) { + logger.debug("Stamina update absolute(" + + reason + ", " + newStamina + ") overridden to absolute(" + + reason + ", " + newStamina + ") by: " + listener.getKey()); + return currentStamina; + } + } + int maxStamina = isCharacterStamina ? getMaxCharacterStamina() : getMaxVehicleStamina(); + if (newStamina < 0) { + newStamina = 0; + } else if (newStamina > maxStamina) { + newStamina = maxStamina; + } + return setStamina(session, reason, newStamina, isCharacterStamina); + } + + // Returns new stamina and sends PlayerPropNotify or VehicleStaminaNotify + public int setStamina(GameSession session, String reason, int newStamina, boolean isCharacterStamina) { + // 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); + session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); + } else { + vehicleStamina = newStamina; + session.send(new PacketVehicleStaminaNotify(vehicleId, ((float) newStamina) / 100)); + } + // notify updated + for (Map.Entry listener : afterUpdateStaminaListeners.entrySet()) { + listener.getValue().onAfterUpdateStamina(reason, newStamina, isCharacterStamina); + } + return newStamina; + } + + // 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 (!player.isPaused() && sustainedStaminaHandlerTimer == null) { + sustainedStaminaHandlerTimer = new Timer(); + sustainedStaminaHandlerTimer.scheduleAtFixedRate(new SustainedStaminaHandler(), 0, 200); + logger.debug("[MovementManager] SustainedStaminaHandlerTimer started"); + } + } + + public void stopSustainedStaminaHandler() { + if (sustainedStaminaHandlerTimer != null) { + sustainedStaminaHandlerTimer.cancel(); + sustainedStaminaHandlerTimer = null; + logger.debug("[MovementManager] SustainedStaminaHandlerTimer stopped"); + } + } + + // Handlers + + // External trigger handler + + public void handleEvtDoSkillSuccNotify(GameSession session, int skillId, int casterId) { + // Ignore if skill not cast by not current active avatar + if (casterId != player.getTeamManager().getCurrentAvatarEntity().getId()) { + return; + } + setSkillCast(skillId, casterId); + // Handle immediate stamina cost + Avatar currentAvatar = player.getTeamManager().getCurrentAvatarEntity().getAvatar(); + if (currentAvatar.getAvatarData().getWeaponType() == WeaponType.WEAPON_CLAYMORE) { + // 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 (BowAvatars.contains(currentAvatarId) + || SwordAvatars.contains(currentAvatarId) + || PolearmAvatars.contains(currentAvatarId) + || CatalystAvatars.contains(currentAvatarId) + ) { + return; + } + */ + //handleImmediateStamina(session, skillId); + } + + public void handleMixinCostStamina(boolean isSwim) { + // Talent moving and claymore avatar charged attack duration + // logger.trace("abilityMixinCostStamina: isSwim: " + isSwim + "\tlastSkill: " + lastSkillId); + 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; + MotionInfo motionInfo = moveInfo.getMotionInfo(); + MotionState motionState = motionInfo.getState(); + int notifyEntityId = entity.getId(); + int currentAvatarEntityId = session.getPlayer().getTeamManager().getCurrentAvatarEntity().getId(); + if (notifyEntityId != currentAvatarEntityId && notifyEntityId != vehicleId) { + return; + } + currentState = motionState; + // logger.trace(currentState + "\t" + (notifyEntityId == currentAvatarEntityId ? "character" : "vehicle")); + 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, motionState); + } + + public void handleVehicleInteractReq(GameSession session, int vehicleId, VehicleInteractType vehicleInteractType) { + if (vehicleInteractType == VehicleInteractType.VEHICLE_INTERACT_TYPE_IN) { + this.vehicleId = vehicleId; + // Reset character stamina here to prevent falling into water immediately on ejection if char stamina is + // close to empty when boarding. + updateStaminaAbsolute(session, "board vehicle", getMaxCharacterStamina(), true); + updateStaminaAbsolute(session, "board vehicle", getMaxVehicleStamina(), false); + } else { + this.vehicleId = -1; + } + } + + // Internal handler + + private void handleImmediateStamina(GameSession session, @NotNull MotionState motionState) { + switch (motionState) { + case MOTION_STATE_CLIMB: + if (currentState != MotionState.MOTION_STATE_CLIMB) { + updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START), true); + } + break; + case MOTION_STATE_DASH_BEFORE_SHAKE: + if (previousState != MotionState.MOTION_STATE_DASH_BEFORE_SHAKE) { + updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT), true); + } + break; + case MOTION_STATE_CLIMB_JUMP: + if (previousState != MotionState.MOTION_STATE_CLIMB_JUMP) { + updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP), true); + } + break; + case MOTION_STATE_SWIM_DASH: + if (previousState != MotionState.MOTION_STATE_SWIM_DASH) { + updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START), true); + } + break; + } + } + + private void handleImmediateStamina(GameSession session, int skillId) { + Consumption consumption = getFightConsumption(skillId); + updateStaminaRelative(session, consumption, true); + } + + private class SustainedStaminaHandler extends TimerTask { + public void run() { + boolean moving = isPlayerMoving(); + int currentCharacterStamina = getCurrentCharacterStamina(); + int maxCharacterStamina = getMaxCharacterStamina(); + int currentVehicleStamina = getCurrentVehicleStamina(); + int maxVehicleStamina = getMaxVehicleStamina(); + if (moving || (currentCharacterStamina < maxCharacterStamina) || (currentVehicleStamina < maxVehicleStamina)) { + logger.trace("Player moving: " + moving + ", stamina full: " + + (currentCharacterStamina >= maxCharacterStamina) + ", recalculate stamina"); + boolean isCharacterStamina = true; + Consumption consumption; + if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { + consumption = getClimbConsumption(); + } else if (MotionStatesCategorized.get("DASH").contains(currentState)) { + consumption = getDashConsumption(); + } else if (MotionStatesCategorized.get("FLY").contains(currentState)) { + consumption = getFlyConsumption(); + } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { + consumption = new Consumption(ConsumptionType.RUN); + } else if (MotionStatesCategorized.get("SKIFF").contains(currentState)) { + consumption = getSkiffConsumption(); + isCharacterStamina = false; + } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { + 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("NOCOST_NORECOVER").contains(currentState)) { + consumption = new Consumption(); + } else if (MotionStatesCategorized.get("OTHER").contains(currentState)) { + consumption = getOtherConsumptions(); + } else { // ignore + return; + } + + if (consumption.amount < 0 && isCharacterStamina) { + // Do not apply reduction factor when recovering stamina + if (player.getTeamManager().getTeamResonances().contains(10301)) { + consumption.amount *= 0.85f; + } + } + // Delay 1 seconds before starts recovering stamina + if (consumption.amount != 0 && cachedSession != null) { + if (consumption.amount < 0) { + staminaRecoverDelay = 0; + } + if (consumption.amount > 0 + && consumption.type != ConsumptionType.POWERED_FLY + && consumption.type != ConsumptionType.POWERED_SKIFF) { + // For POWERED_* recover immediately - things like Amber's gliding exam and skiff challenges may require this. + if (staminaRecoverDelay < 5) { + // For others recover after 1 seconds (5 ticks) - as official server does. + staminaRecoverDelay++; + consumption.amount = 0; + logger.trace("Delaying recovery: " + staminaRecoverDelay); + } + } + updateStaminaRelative(cachedSession, consumption, isCharacterStamina); + } + } + previousState = currentState; + previousCoordinates = new Position( + currentCoordinates.getX(), + currentCoordinates.getY(), + currentCoordinates.getZ() + ); + } + } + + private void handleDrowning() { + // TODO: fix drowning waverider entity + int stamina = getCurrentCharacterStamina(); + if (stamina < 10) { + logger.trace(getCurrentCharacterStamina() + "/" + + getMaxCharacterStamina() + "\t" + currentState); + if (currentState != MotionState.MOTION_STATE_SWIM_IDLE) { + killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_TYPE_DRAWN); + } + } + } + + // Consumption Calculators + + // Stamina Consumption Reduction: https://genshin-impact.fandom.com/wiki/Stamina + + private Consumption getFightConsumption(int skillCasting) { + // Talent moving + if (TalentMovements.contains(skillCasting)) { + // TODO: recover 1000 if kamisato hits an enemy at the end of dashing + return getTalentMovingSustainedCost(skillCasting); + } + // Bow avatar charged attack + Avatar currentAvatar = player.getTeamManager().getCurrentAvatarEntity().getAvatar(); + + switch (currentAvatar.getAvatarData().getWeaponType()) { + case WEAPON_BOW: + return getBowSustainedCost(skillCasting); + case WEAPON_CLAYMORE: + return getClaymoreSustainedCost(skillCasting); + case WEAPON_CATALYST: + return getCatalystCost(skillCasting); + case WEAPON_POLE: + return getPolearmCost(skillCasting); + case WEAPON_SWORD_ONE_HAND: + return getSwordCost(skillCasting); + } + + return new Consumption(); + } + + private Consumption getClimbConsumption() { + Consumption consumption = new Consumption(); + if (currentState == MotionState.MOTION_STATE_CLIMB && isPlayerMoving()) { + consumption.type = ConsumptionType.CLIMBING; + consumption.amount = ConsumptionType.CLIMBING.amount; + } + // Climbing specific reductions + consumption.amount *= getFoodCostReductionFactor(ClimbFoodReductionMap); + consumption.amount *= getTalentCostReductionFactor(ClimbTalentReductionMap); + return consumption; + } + + private Consumption getSwimConsumptions() { + handleDrowning(); + Consumption consumption = new Consumption(); + if (currentState == MotionState.MOTION_STATE_SWIM_MOVE) { + consumption.type = ConsumptionType.SWIMMING; + consumption.amount = ConsumptionType.SWIMMING.amount; + } + if (currentState == MotionState.MOTION_STATE_SWIM_DASH) { + consumption.type = ConsumptionType.SWIM_DASH; + consumption.amount = ConsumptionType.SWIM_DASH.amount; + } + // Swimming specific reductions + consumption.amount *= getFoodCostReductionFactor(SwimFoodReductionMap); + consumption.amount *= getTalentCostReductionFactor(SwimTalentReductionMap); + return consumption; + } + + private Consumption getDashConsumption() { + Consumption consumption = new Consumption(); + if (currentState == MotionState.MOTION_STATE_DASH) { + consumption.type = ConsumptionType.DASH; + consumption.amount = ConsumptionType.DASH.amount; + // Dashing specific reductions + consumption.amount *= getFoodCostReductionFactor(DashFoodReductionMap); + } + return consumption; + } + + private Consumption getFlyConsumption() { + // POWERED_FLY, e.g. wind tunnel + if (currentState == MotionState.MOTION_STATE_POWERED_FLY) { + return new Consumption(ConsumptionType.POWERED_FLY); + } + Consumption consumption = new Consumption(ConsumptionType.FLY); + // Flying specific reductions + consumption.amount *= getFoodCostReductionFactor(FlyFoodReductionMap); + consumption.amount *= getTalentCostReductionFactor(FlyTalentReductionMap); + return consumption; + } + + private Consumption getSkiffConsumption() { + // No known reduction for skiffing. + return switch (currentState) { + case MOTION_STATE_SKIFF_DASH -> new Consumption(ConsumptionType.SKIFF_DASH); + case MOTION_STATE_SKIFF_POWERED_DASH -> new Consumption(ConsumptionType.POWERED_SKIFF); + case MOTION_STATE_SKIFF_NORMAL -> new Consumption(ConsumptionType.SKIFF); + default -> new Consumption(); + }; + } + + private Consumption getOtherConsumptions() { + switch (currentState) { + case MOTION_STATE_NOTIFY: +// if (BowSkills.contains(lastSkillId)) { +// return new Consumption(ConsumptionType.FIGHT, 500); +// } + break; + case MOTION_STATE_FIGHT: + // TODO: what if charged attack + return new Consumption(ConsumptionType.FIGHT, 500); + } + + 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 (talentReductionMap.containsKey(skillId)) { + float potentialLowerReduction = talentReductionMap.get(skillId); + if (potentialLowerReduction < reduction) { + reduction = potentialLowerReduction; + } + } + } + } + return reduction; + } + + 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; + } + + 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 getCatalystCost(int skillId) { + Consumption consumption = new Consumption(ConsumptionType.FIGHT, -5000); + // Character specific handling + switch (skillId) { + // TODO: + } + return consumption; + } + + private Consumption getClaymoreSustainedCost(int skillId) { + Consumption consumption = new Consumption(ConsumptionType.FIGHT, -1333); // 4000 / 3 = 1333 + // Character specific handling + switch (skillId) { + case 10571: + case 10532: + consumption.amount = 0; + break; + case 10160: + if (player.getTeamManager().getCurrentAvatarEntity().getAvatar().getProudSkillList().contains(162101)) { + consumption.amount /= 2; + } + break; + } + return consumption; + } + + private Consumption getPolearmCost(int skillId) { + Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2500); + // Character specific handling + switch (skillId) { + // TODO: + } + return consumption; + } + + private Consumption getSwordCost(int skillId) { + Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2000); + // Character specific handling + switch (skillId) { + case 10421: + consumption.amount = -2500; + break; + } + return consumption; + } +} diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 5f1c93bff..28d2f4d54 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -14,7 +14,6 @@ import emu.grasscutter.game.avatar.AvatarProfileData; import emu.grasscutter.game.avatar.AvatarStorage; import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.EntityVehicle; -import emu.grasscutter.game.managers.DeforestationManager.DeforestationManager; import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityItem; import emu.grasscutter.game.entity.GameEntity; @@ -28,18 +27,19 @@ import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.mail.MailHandler; import emu.grasscutter.game.managers.InsectCaptureManager; import emu.grasscutter.game.managers.ResinManager; -import emu.grasscutter.game.managers.StaminaManager.StaminaManager; +import emu.grasscutter.game.managers.deforestation.DeforestationManager; +import emu.grasscutter.game.managers.energy.EnergyManager; +import emu.grasscutter.game.managers.forging.ActiveForgeData; +import emu.grasscutter.game.managers.forging.ForgingManager; +import emu.grasscutter.game.managers.mapmark.*; +import emu.grasscutter.game.managers.stamina.StaminaManager; import emu.grasscutter.game.managers.SotSManager; -import emu.grasscutter.game.managers.EnergyManager.EnergyManager; -import emu.grasscutter.game.managers.ForgingManager.ActiveForgeData; -import emu.grasscutter.game.managers.ForgingManager.ForgingManager; 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.QuestManager; import emu.grasscutter.game.shop.ShopLimit; -import emu.grasscutter.game.managers.MapMarkManager.*; import emu.grasscutter.game.tower.TowerData; import emu.grasscutter.game.tower.TowerManager; import emu.grasscutter.game.world.Scene; diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index 4069a2f84..3a93150d1 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -10,10 +10,10 @@ import emu.grasscutter.game.drop.DropManager; import emu.grasscutter.game.dungeons.DungeonManager; import emu.grasscutter.game.expedition.ExpeditionManager; import emu.grasscutter.game.gacha.GachaManager; -import emu.grasscutter.game.managers.ChatManager.ChatManager; -import emu.grasscutter.game.managers.ChatManager.ChatManagerHandler; import emu.grasscutter.game.managers.InventoryManager; import emu.grasscutter.game.managers.MultiplayerManager; +import emu.grasscutter.game.managers.chat.ChatManager; +import emu.grasscutter.game.managers.chat.ChatManagerHandler; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.quest.ServerQuestHandler; import emu.grasscutter.game.shop.ShopManager; diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketMarkMapRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketMarkMapRsp.java index 21ccbe74e..337e28e0b 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketMarkMapRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketMarkMapRsp.java @@ -1,6 +1,6 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.game.managers.MapMarkManager.MapMark; +import emu.grasscutter.game.managers.mapmark.MapMark; import emu.grasscutter.game.player.Player; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes;