diff --git a/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java b/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java index a21e14360..82efb795f 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityAvatar.java @@ -104,6 +104,11 @@ public class EntityAvatar extends GameEntity { this.killedType = PlayerDieType.PLAYER_DIE_KILL_BY_MONSTER; this.killedBy = killerId; } + + public void onDeath(PlayerDieType dieType, int killerId) { + this.killedType = dieType; + this.killedBy = killerId; + } public SceneAvatarInfo getSceneAvatarInfo() { SceneAvatarInfo.Builder avatarInfo = SceneAvatarInfo.newBuilder() diff --git a/src/main/java/emu/grasscutter/game/managers/MotionManager/MotionManager.java b/src/main/java/emu/grasscutter/game/managers/MotionManager/MotionManager.java deleted file mode 100644 index d8d6f25b0..000000000 --- a/src/main/java/emu/grasscutter/game/managers/MotionManager/MotionManager.java +++ /dev/null @@ -1,227 +0,0 @@ -package emu.grasscutter.game.managers.MotionManager; - -import emu.grasscutter.Grasscutter; -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.net.proto.EntityMoveInfoOuterClass; -import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; -import emu.grasscutter.net.proto.VectorOuterClass; -import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; -import emu.grasscutter.server.packet.send.PacketLifeStateChangeNotify; -import emu.grasscutter.server.packet.send.PacketPlayerPropNotify; -import emu.grasscutter.utils.Position; - -import java.util.ArrayList; -import java.lang.Math; - -public class MotionManager { - - private enum Consumption { - None(0), - - // consumers - CLIMB_START(-500), - CLIMBING(-150), - CLIMB_JUMP(-2500), - DASH(-1800), - SPRINT(-360), - FLY(-60), - SWIM_DASH_START(-200), - SWIM_DASH(-200), - SWIMMING(-80), - - // restorers - STANDBY(500), - RUN(500), - WALK(500), - STANDBY_MOVE(500); - - public final int amount; - Consumption(int amount) { - this.amount = amount; - } - } - - private EntityMoveInfoOuterClass.EntityMoveInfo moveInfo; - - private MotionState previousState = MotionState.MOTION_STANDBY; - private ArrayList previousCoordinates = new ArrayList<>(); - private final Player player; - - private float landSpeed = 0; - - public MotionManager(Player player) { - previousCoordinates.add(new Position(0,0,0)); - this.player = player; - } - - public void handle(GameSession session, GameEntity entity, EntityMoveInfoOuterClass.EntityMoveInfo moveInfo) { - MotionState state = moveInfo.getMotionInfo().getState(); - setMoveInfo(moveInfo); - if (state == MotionState.MOTION_LAND_SPEED) { - setLandSpeed(moveInfo.getMotionInfo().getSpeed().getY()); - } - if (state == MotionState.MOTION_FALL_ON_GROUND) { - handleFallOnGround(session, entity); - } - } - - public void tick() { - if(Grasscutter.getConfig().OpenStamina){ - EntityMoveInfoOuterClass.EntityMoveInfo mInfo = moveInfo; - if (mInfo == null) { - return; - } - - MotionState state = moveInfo.getMotionInfo().getState(); - Consumption consumption = Consumption.None; - - boolean isMoving = false; - VectorOuterClass.Vector posVector = moveInfo.getMotionInfo().getPos(); - Position currentCoordinates = new Position(posVector.getX(), posVector.getY(), posVector.getZ()); - - float diffX = currentCoordinates.getX() - previousCoordinates.get(0).getX(); - float diffY = currentCoordinates.getY() - previousCoordinates.get(0).getY(); - float diffZ = currentCoordinates.getZ() - previousCoordinates.get(0).getZ(); - - if (Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.3 || Math.abs(diffZ) > 0.3) { - isMoving = true; - } - - if (isMoving) { - // TODO: refactor these conditions. - // CLIMB - if (state == MotionState.MOTION_CLIMB) { - if (previousState != MotionState.MOTION_CLIMB && previousState != MotionState.MOTION_CLIMB_JUMP) { - consumption = Consumption.CLIMB_START; - } else { - consumption = Consumption.CLIMBING; - } - } - // JUMP - if (state == MotionState.MOTION_CLIMB_JUMP) { - if (previousState != MotionState.MOTION_CLIMB_JUMP) { - consumption = Consumption.CLIMB_JUMP; - } - } - if (state == MotionState.MOTION_JUMP) { - if (previousState == MotionState.MOTION_CLIMB) { - consumption = Consumption.CLIMB_JUMP; - } - } - // SWIM - if (state == MotionState.MOTION_SWIM_MOVE) { - consumption = Consumption.SWIMMING; - } - if (state == MotionState.MOTION_SWIM_DASH) { - if (previousState != MotionState.MOTION_SWIM_DASH) { - consumption = Consumption.SWIM_DASH_START; - } else { - consumption = Consumption.SWIM_DASH; - } - } - // DASH - if (state == MotionState.MOTION_DASH) { - if (previousState == MotionState.MOTION_DASH) { - consumption = Consumption.SPRINT; - } else { - consumption = Consumption.DASH; - } - } - // RUN and WALK - if (state == MotionState.MOTION_RUN) { - consumption = Consumption.RUN; - } - if (state == MotionState.MOTION_WALK) { - consumption = Consumption.WALK; - } - // FLY - if (state == MotionState.MOTION_FLY) { - consumption = Consumption.FLY; - } - } - // STAND - if (state == MotionState.MOTION_STANDBY) { - consumption = Consumption.STANDBY; - } - if (state == MotionState.MOTION_STANDBY_MOVE) { - consumption = Consumption.STANDBY_MOVE; - } - - GameSession session = player.getSession(); - updateStamina(session, consumption.amount); - session.send(new PacketPlayerPropNotify(session.getPlayer(), PlayerProperty.PROP_CUR_PERSIST_STAMINA)); - - Grasscutter.getLogger().debug(session.getPlayer().getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + " " + state + " " + isMoving + " " + consumption + " " + consumption.amount); - - previousState = state; - previousCoordinates.add(currentCoordinates); - if (previousCoordinates.size() > 3) { - previousCoordinates.remove(0); - } - } - } - - public void updateStamina(GameSession session, int amount) { - if (amount == 0) { - return; - } - int currentStamina = session.getPlayer().getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); - int playerMaxStamina = session.getPlayer().getProperty(PlayerProperty.PROP_MAX_STAMINA); - int newStamina = currentStamina + amount; - if (newStamina < 0) { - newStamina = 0; - } - if (newStamina > playerMaxStamina) { - newStamina = playerMaxStamina; - } - session.getPlayer().setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); - } - - public void setMoveInfo(EntityMoveInfoOuterClass.EntityMoveInfo moveInfo) { - this.moveInfo = moveInfo; - } - - public EntityMoveInfoOuterClass.EntityMoveInfo getMoveInfo() { - return moveInfo; - } - - public void handleFallOnGround(GameSession session, GameEntity entity) { - float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); - float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); - float damage = 0; - Grasscutter.getLogger().debug("LandSpeed: " + landSpeed); - if (landSpeed < -23.5) { - damage = (float)(maxHP * 0.33); - } - if (landSpeed < -25) { - damage = (float)(maxHP * 0.5); - } - if (landSpeed < -26.5) { - damage = (float)(maxHP * 0.66); - } - if (landSpeed < -28) { - damage = (maxHP * 1); - } - float newHP = currentHP - damage; - if (newHP < 0) { - newHP = 0; - } - Grasscutter.getLogger().debug("Max: " + maxHP + "\tCurr: " + currentHP + "\tDamage: " + damage + "\tnewHP: " + newHP); - entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); - entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); - if (newHP == 0) { - entity.getWorld().broadcastPacket(new PacketLifeStateChangeNotify(0, entity, LifeState.LIFE_DEAD)); - session.getPlayer().getScene().removeEntity(entity); - entity.onDeath(0); - } - } - - public void setLandSpeed(float landSpeed) { - this.landSpeed = landSpeed; - } -} diff --git a/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java b/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java new file mode 100644 index 000000000..a08280378 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java @@ -0,0 +1,359 @@ +package emu.grasscutter.game.managers.MovementManager; + +import emu.grasscutter.Grasscutter; +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.net.proto.EntityMoveInfoOuterClass; +import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; +import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; +import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; +import emu.grasscutter.net.proto.VectorOuterClass; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.*; +import emu.grasscutter.utils.Position; +import org.jetbrains.annotations.NotNull; + +import java.lang.Math; +import java.util.*; + +public class MovementManager { + + public HashMap> MotionStatesCategorized = new HashMap<>(); + + private enum Consumption { + None(0), + + // consume + CLIMB_START(-500), + CLIMBING(-150), + CLIMB_JUMP(-2500), + DASH(-1800), + SPRINT(-360), + FLY(-60), + SWIM_DASH_START(-200), + SWIM_DASH(-200), + SWIMMING(-80), + + // restore + STANDBY(500), + RUN(500), + WALK(500), + STANDBY_MOVE(500), + POWERED_FLY(500); + + public final int amount; + Consumption(int amount) { + this.amount = amount; + } + } + + + private MotionState previousState = MotionState.MOTION_STANDBY; + private MotionState currentState = MotionState.MOTION_STANDBY; + private Position previousCoordinates = new Position(0, 0, 0); + private Position currentCoordinates = new Position(0, 0, 0); + + private final Player player; + + private float landSpeed = 0; + private Timer movementManagerTickTimer; + private GameSession cachedSession = null; + private GameEntity cachedEntity = null; + + private int staminaRecoverDelay = 0; + + public MovementManager(Player player) { + previousCoordinates.add(new Position(0,0,0)); + this.player = player; + + MotionStatesCategorized.put("SWIM", new HashSet<>(Arrays.asList( + MotionState.MOTION_SWIM_MOVE, + MotionState.MOTION_SWIM_IDLE, + MotionState.MOTION_SWIM_DASH, + MotionState.MOTION_SWIM_JUMP + ))); + + MotionStatesCategorized.put("STANDBY", new HashSet<>(Arrays.asList( + MotionState.MOTION_STANDBY, + MotionState.MOTION_STANDBY_MOVE, + MotionState.MOTION_DANGER_STANDBY, + MotionState.MOTION_DANGER_STANDBY_MOVE, + MotionState.MOTION_LADDER_TO_STANDBY, + MotionState.MOTION_JUMP_UP_WALL_FOR_STANDBY + ))); + + MotionStatesCategorized.put("CLIMB", new HashSet<>(Arrays.asList( + MotionState.MOTION_CLIMB, + MotionState.MOTION_CLIMB_JUMP, + MotionState.MOTION_STANDBY_TO_CLIMB, + MotionState.MOTION_LADDER_IDLE, + MotionState.MOTION_LADDER_MOVE, + MotionState.MOTION_LADDER_SLIP, + MotionState.MOTION_STANDBY_TO_LADDER + ))); + + MotionStatesCategorized.put("FLY", new HashSet<>(Arrays.asList( + MotionState.MOTION_FLY, + MotionState.MOTION_FLY_IDLE, + MotionState.MOTION_FLY_SLOW, + MotionState.MOTION_FLY_FAST, + MotionState.MOTION_POWERED_FLY + ))); + + MotionStatesCategorized.put("RUN", new HashSet<>(Arrays.asList( + MotionState.MOTION_DASH, + MotionState.MOTION_DANGER_DASH, + MotionState.MOTION_DASH_BEFORE_SHAKE, + MotionState.MOTION_RUN, + MotionState.MOTION_DANGER_RUN, + MotionState.MOTION_WALK, + MotionState.MOTION_DANGER_WALK + ))); + } + + public void handle(GameSession session, EntityMoveInfoOuterClass.EntityMoveInfo moveInfo, GameEntity entity) { + if (movementManagerTickTimer == null) { + movementManagerTickTimer = new Timer(); + movementManagerTickTimer.scheduleAtFixedRate(new MotionManagerTick(), 0, 200); + } + // cache info for later use in tick + cachedSession = session; + cachedEntity = entity; + + MotionInfo motionInfo = moveInfo.getMotionInfo(); + moveEntity(entity, moveInfo); + VectorOuterClass.Vector posVector = motionInfo.getPos(); + Position newPos = new Position(posVector.getX(), + posVector.getY(), posVector.getZ());; + if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) { + currentCoordinates = newPos; + } + currentState = motionInfo.getState(); + Grasscutter.getLogger().debug("" + currentState); + handleFallOnGround(motionInfo); + } + + public void resetTimer() { + movementManagerTickTimer.cancel(); + movementManagerTickTimer = null; + } + + private void moveEntity(GameEntity entity, EntityMoveInfoOuterClass.EntityMoveInfo moveInfo) { + entity.getPosition().set(moveInfo.getMotionInfo().getPos()); + entity.getRotation().set(moveInfo.getMotionInfo().getRot()); + entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime()); + entity.setLastMoveReliableSeq(moveInfo.getReliableSeq()); + entity.setMotionState(moveInfo.getMotionInfo().getState()); + } + + private boolean isPlayerMoving() { + float diffX = currentCoordinates.getX() - previousCoordinates.getX(); + float diffY = currentCoordinates.getY() - previousCoordinates.getY(); + float diffZ = currentCoordinates.getZ() - previousCoordinates.getZ(); + // Grasscutter.getLogger().debug("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates + ", " + diffX + ", " + diffY + ", " + diffZ); + return Math.abs(diffX) > 0.2 || Math.abs(diffY) > 0.1 || Math.abs(diffZ) > 0.2; + } + + private int getCurrentStamina() { + return player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + } + + private int getMaximumStamina() { + return player.getProperty(PlayerProperty.PROP_MAX_STAMINA); + } + + + + // Returns new stamina + public int updateStamina(GameSession session, int amount) { + int currentStamina = session.getPlayer().getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + if (amount == 0) { + return currentStamina; + } + int playerMaxStamina = session.getPlayer().getProperty(PlayerProperty.PROP_MAX_STAMINA); + int newStamina = currentStamina + amount; + if (newStamina < 0) { + newStamina = 0; + } + if (newStamina > playerMaxStamina) { + newStamina = playerMaxStamina; + } + session.getPlayer().setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); + return newStamina; + } + + private void handleFallOnGround(@NotNull MotionInfo motionInfo) { + MotionState state = motionInfo.getState(); + // land speed and fall on ground event arrive in different packets + // cache land speed + if (state == MotionState.MOTION_LAND_SPEED) { + landSpeed = motionInfo.getSpeed().getY(); + } + if (state == MotionState.MOTION_FALL_ON_GROUND) { + float currentHP = cachedEntity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + float maxHP = cachedEntity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + float damage = 0; +// Grasscutter.getLogger().debug("LandSpeed: " + landSpeed); + if (landSpeed < -23.5) { + damage = (float)(maxHP * 0.33); + } + if (landSpeed < -25) { + damage = (float)(maxHP * 0.5); + } + if (landSpeed < -26.5) { + damage = (float)(maxHP * 0.66); + } + if (landSpeed < -28) { + damage = (maxHP * 1); + } + float newHP = currentHP - damage; + if (newHP < 0) { + newHP = 0; + } +// Grasscutter.getLogger().debug("Max: " + maxHP + "\tCurr: " + currentHP + "\tDamage: " + damage + "\tnewHP: " + newHP); + cachedEntity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); + cachedEntity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(cachedEntity, FightProperty.FIGHT_PROP_CUR_HP)); + if (newHP == 0) { + killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_FALL); + } + landSpeed = 0; + } + } + + private void handleDrowning() { + int stamina = getCurrentStamina(); + if (stamina < 10) { + boolean isSwimming = MotionStatesCategorized.get("SWIM").contains(currentState); + Grasscutter.getLogger().debug(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState + "\t" + isSwimming); + if (isSwimming && currentState != MotionState.MOTION_SWIM_IDLE) { + killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_DRAWN); + } + } + } + + public void killAvatar(GameSession session, GameEntity entity, PlayerDieType dieType) { + cachedSession.send(new PacketAvatarLifeStateChangeNotify( + cachedSession.getPlayer().getTeamManager().getCurrentAvatarEntity().getAvatar(), + LifeState.LIFE_DEAD, + dieType + )); + cachedSession.send(new PacketLifeStateChangeNotify( + cachedEntity, + LifeState.LIFE_DEAD, + dieType + )); + cachedEntity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0); + cachedEntity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(cachedEntity, FightProperty.FIGHT_PROP_CUR_HP)); + entity.getWorld().broadcastPacket(new PacketLifeStateChangeNotify(0, entity, LifeState.LIFE_DEAD)); + session.getPlayer().getScene().removeEntity(entity); + ((EntityAvatar)entity).onDeath(dieType, 0); + } + + private class MotionManagerTick extends TimerTask + { + public void run() { + if (Grasscutter.getConfig().OpenStamina) { + boolean moving = isPlayerMoving(); + if (moving || (getCurrentStamina() < getMaximumStamina())) { + Grasscutter.getLogger().debug("Player moving: " + moving + ", stamina full: " + (getCurrentStamina() >= getMaximumStamina()) + ", recalculate stamina"); + Consumption consumption = Consumption.None; + + // TODO: refactor these conditions. + if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { + if (currentState == MotionState.MOTION_CLIMB) { + // CLIMB + if (previousState != MotionState.MOTION_CLIMB && previousState != MotionState.MOTION_CLIMB_JUMP) { + consumption = Consumption.CLIMB_START; + } else { + consumption = Consumption.CLIMBING; + } + } + if (currentState == MotionState.MOTION_CLIMB_JUMP) { + if (previousState != MotionState.MOTION_CLIMB_JUMP) { + consumption = Consumption.CLIMB_JUMP; + } + } + if (currentState == MotionState.MOTION_JUMP) { + if (previousState == MotionState.MOTION_CLIMB) { + consumption = Consumption.CLIMB_JUMP; + } + } + } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { + // SWIM + if (currentState == MotionState.MOTION_SWIM_MOVE) { + consumption = Consumption.SWIMMING; + } + if (currentState == MotionState.MOTION_SWIM_DASH) { + if (previousState != MotionState.MOTION_SWIM_DASH) { + consumption = Consumption.SWIM_DASH_START; + } else { + consumption = Consumption.SWIM_DASH; + } + } + } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { + // RUN, DASH and WALK + // DASH + if (currentState == MotionState.MOTION_DASH) { + if (previousState == MotionState.MOTION_DASH) { + consumption = Consumption.SPRINT; + } else { + // cost more to start dashing + consumption = Consumption.DASH; + } + } + // RUN + if (currentState == MotionState.MOTION_RUN) { + consumption = Consumption.RUN; + } + // WALK + if (currentState == MotionState.MOTION_WALK) { + consumption = Consumption.WALK; + } + } else if (MotionStatesCategorized.get("FLY").contains(currentState)) { + // FLY + consumption = Consumption.FLY; + // POWERED_FLY, e.g. wind tunnel + if (currentState == MotionState.MOTION_POWERED_FLY) { + consumption = Consumption.POWERED_FLY; + } + } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { + // STAND + if (currentState == MotionState.MOTION_STANDBY) { + consumption = Consumption.STANDBY; + } + if (currentState == MotionState.MOTION_STANDBY_MOVE) { + consumption = Consumption.STANDBY_MOVE; + } + } + + // tick triggered + handleDrowning(); + + if (cachedSession != null) { + if (consumption.amount < 0) { + staminaRecoverDelay = 0; + } + if (consumption.amount > 0) { + if (staminaRecoverDelay < 5) { + staminaRecoverDelay++; + consumption = Consumption.None; + } + } + int newStamina = updateStamina(cachedSession, consumption.amount); + cachedSession.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); + + Grasscutter.getLogger().debug(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState + "\t" + "isMoving: " + isPlayerMoving() + "\t" + consumption + "(" + consumption.amount + ")"); + } + } + } + + previousState = currentState; + previousCoordinates = new Position(currentCoordinates.getX(), + currentCoordinates.getY(), currentCoordinates.getZ());; + } + } +} diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index b1abda002..f6ea68dc6 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -21,7 +21,7 @@ import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.mail.MailHandler; -import emu.grasscutter.game.managers.MotionManager.MotionManager; +import emu.grasscutter.game.managers.MovementManager.MovementManager; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.PlayerProperty; @@ -122,7 +122,7 @@ public class Player { @Transient private final InvokeHandler clientAbilityInitFinishHandler; private MapMarksManager mapMarksManager; - @Transient private MotionManager motionManager; + @Transient private MovementManager movementManager; @Deprecated @@ -164,7 +164,7 @@ public class Player { this.shopLimit = new ArrayList<>(); this.messageHandler = null; this.mapMarksManager = new MapMarksManager(); - this.motionManager = new MotionManager(this); + this.movementManager = new MovementManager(this); } // On player creation @@ -191,7 +191,7 @@ public class Player { this.getRotation().set(0, 307, 0); this.messageHandler = null; this.mapMarksManager = new MapMarksManager(); - this.motionManager = new MotionManager(this); + this.movementManager = new MovementManager(this); } public int getUid() { @@ -974,7 +974,7 @@ public class Player { return mapMarksManager; } - public MotionManager getMotionManager() { return motionManager; } + public MovementManager getMovementManager() { return movementManager; } public synchronized void onTick() { // Check ping @@ -1004,33 +1004,9 @@ public class Player { this.resetSendPlayerLocTime(); } } - - scheduleStaminaNotify(); } - private void scheduleStaminaNotify() { - // stamina tick - EntityMoveInfoOuterClass.EntityMoveInfo moveInfo = getMotionManager().getMoveInfo(); - if (moveInfo == null) { - return; - } - if (getMotionManager().getMoveInfo().getMotionInfo().getState() == MotionStateOuterClass.MotionState.MOTION_STANDBY) { - if (getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) == getProperty(PlayerProperty.PROP_MAX_STAMINA) ) { - return; - } - } - - for (int i = 0; i <= 1000; i+=200) { - Timer timer = new Timer(); - timer.schedule(new TimerTask() { - @Override - public void run() { - getMotionManager().tick(); - } - }, i); - } - } public void resetSendPlayerLocTime() { diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index 666c44dee..ff9a3c68d 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -24,6 +24,7 @@ import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType; import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; +import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; import emu.grasscutter.server.packet.send.PacketAvatarDieAnimationEndRsp; import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify; @@ -419,27 +420,37 @@ public class TeamManager { if (deadAvatar.isAlive() || deadAvatar.getId() != dieGuid) { return; } - - // Replacement avatar - EntityAvatar replacement = null; - int replaceIndex = -1; - - for (int i = 0; i < this.getActiveTeam().size(); i++) { - EntityAvatar entity = this.getActiveTeam().get(i); - if (entity.isAlive()) { - replaceIndex = i; - replacement = entity; - break; - } - } - - if (replacement == null) { - // No more living team members... - getPlayer().sendPacket(new PacketWorldPlayerDieNotify(deadAvatar.getKilledType(), deadAvatar.getKilledBy())); + + PlayerDieType dieType = deadAvatar.getKilledType(); + int killedBy = deadAvatar.getKilledBy(); + + if (dieType == PlayerDieType.PLAYER_DIE_DRAWN) { + // Died in water. Do not replace + // The official server has skipped this notify and will just respawn the team immediately after the animation. + // TODO: Perhaps find a way to get vanilla experience? + getPlayer().sendPacket(new PacketWorldPlayerDieNotify(dieType, killedBy)); } else { - // Set index and spawn replacement member - this.setCurrentCharacterIndex(replaceIndex); - getPlayer().getScene().addEntity(replacement); + // Replacement avatar + EntityAvatar replacement = null; + int replaceIndex = -1; + + for (int i = 0; i < this.getActiveTeam().size(); i++) { + EntityAvatar entity = this.getActiveTeam().get(i); + if (entity.isAlive()) { + replaceIndex = i; + replacement = entity; + break; + } + } + + if (replacement == null) { + // No more living team members... + getPlayer().sendPacket(new PacketWorldPlayerDieNotify(dieType, killedBy)); + } else { + // Set index and spawn replacement member + this.setCurrentCharacterIndex(replaceIndex); + getPlayer().getScene().addEntity(replacement); + } } // Response packet @@ -492,11 +503,13 @@ public class TeamManager { public void respawnTeam() { // Make sure all team members are dead - for (EntityAvatar entity : getActiveTeam()) { - if (entity.isAlive()) { - return; - } - } + // Drowning needs revive when there may be other team members still alive. + // for (EntityAvatar entity : getActiveTeam()) { + // if (entity.isAlive()) { + // return; + // } + // } + player.getMovementManager().resetTimer(); // prevent drowning immediately after respawn // Revive all team members for (EntityAvatar entity : getActiveTeam()) { diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java index 878997e22..27e4ca6ff 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java @@ -1,9 +1,6 @@ package emu.grasscutter.server.packet.recv; import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.game.managers.MotionManager.MotionManager; -import emu.grasscutter.game.props.FightProperty; -import emu.grasscutter.game.props.LifeState; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify; @@ -11,9 +8,7 @@ import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry; import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo; import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo; import emu.grasscutter.net.packet.PacketHandler; -import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; import emu.grasscutter.server.game.GameSession; -import emu.grasscutter.server.packet.send.*; @Opcodes(PacketOpcodes.CombatInvocationsNotify) public class HandlerCombatInvocationsNotify extends PacketHandler { @@ -33,13 +28,7 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { EntityMoveInfo moveInfo = EntityMoveInfo.parseFrom(entry.getCombatData()); GameEntity entity = session.getPlayer().getScene().getEntityById(moveInfo.getEntityId()); if (entity != null) { - //move - entity.getPosition().set(moveInfo.getMotionInfo().getPos()); - entity.getRotation().set(moveInfo.getMotionInfo().getRot()); - entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime()); - entity.setLastMoveReliableSeq(moveInfo.getReliableSeq()); - entity.setMotionState(moveInfo.getMotionInfo().getState()); - session.getPlayer().getMotionManager().handle(session, entity, moveInfo); + session.getPlayer().getMovementManager().handle(session, moveInfo, entity); } break; default: @@ -52,7 +41,7 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { if (notif.getInvokeListList().size() > 0) { session.getPlayer().getCombatInvokeHandler().update(session.getPlayer()); } - // Handle attack results last + // Handle attack results last while (!session.getPlayer().getAttackResults().isEmpty()) { session.getPlayer().getScene().handleAttack(session.getPlayer().getAttackResults().poll()); } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerWorldPlayerReviveReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerWorldPlayerReviveReq.java index cbe95a322..6872dd270 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerWorldPlayerReviveReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerWorldPlayerReviveReq.java @@ -3,7 +3,9 @@ package emu.grasscutter.server.packet.recv; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.proto.WorldPlayerDieNotifyOuterClass; import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketWorldPlayerReviveRsp; @Opcodes(PacketOpcodes.WorldPlayerReviveReq) public class HandlerWorldPlayerReviveReq extends PacketHandler { @@ -11,6 +13,7 @@ public class HandlerWorldPlayerReviveReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { session.getPlayer().getTeamManager().respawnTeam(); + session.send(new PacketWorldPlayerReviveRsp()); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarLifeStateChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarLifeStateChangeNotify.java index 3032bf355..d7258f6fa 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarLifeStateChangeNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAvatarLifeStateChangeNotify.java @@ -9,6 +9,10 @@ import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.AvatarLifeStateChangeNotifyOuterClass.AvatarLifeStateChangeNotify; import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; +import emu.grasscutter.net.proto.ServerBuffOuterClass; +import emu.grasscutter.net.proto.ServerBuffOuterClass.ServerBuff; + +import java.util.ArrayList; public class PacketAvatarLifeStateChangeNotify extends BasePacket { @@ -22,7 +26,7 @@ public class PacketAvatarLifeStateChangeNotify extends BasePacket { this.setData(proto); } - public PacketAvatarLifeStateChangeNotify(Avatar avatar,int attackerId,LifeState lifeState) { + public PacketAvatarLifeStateChangeNotify(Avatar avatar, int attackerId, LifeState lifeState) { super(PacketOpcodes.AvatarLifeStateChangeNotify); AvatarLifeStateChangeNotify proto = AvatarLifeStateChangeNotify.newBuilder() @@ -33,4 +37,26 @@ public class PacketAvatarLifeStateChangeNotify extends BasePacket { this.setData(proto); } + + public PacketAvatarLifeStateChangeNotify(Avatar avatar, LifeState lifeState, PlayerDieType dieType) { + this(avatar, lifeState, null, "", dieType); + } + + public PacketAvatarLifeStateChangeNotify(Avatar avatar, LifeState lifeState, GameEntity sourceEntity, + String attackTag, PlayerDieType dieType) { + super(PacketOpcodes.AvatarLifeStateChangeNotify); + + AvatarLifeStateChangeNotify.Builder proto = AvatarLifeStateChangeNotify.newBuilder(); + + proto.setAvatarGuid(avatar.getGuid()); + proto.setLifeState(lifeState.getValue()); + if (sourceEntity != null) { + proto.setSourceEntityId(sourceEntity.getId()); + } + proto.setDieType(dieType); + proto.setAttackTag((attackTag)); + + this.setData(proto.build()); + } + } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketLifeStateChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketLifeStateChangeNotify.java index c70d117d7..75685a27e 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketLifeStateChangeNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketLifeStateChangeNotify.java @@ -1,10 +1,15 @@ package emu.grasscutter.server.packet.send; +import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.props.LifeState; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.LifeStateChangeNotifyOuterClass.LifeStateChangeNotify; +import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; +import emu.grasscutter.net.proto.ServerBuffOuterClass.ServerBuff; + +import java.util.ArrayList; public class PacketLifeStateChangeNotify extends BasePacket { public PacketLifeStateChangeNotify(GameEntity attacker, GameEntity target, LifeState lifeState) { @@ -26,7 +31,29 @@ public class PacketLifeStateChangeNotify extends BasePacket { .setLifeState(lifeState.getValue()) .setSourceEntityId(attackerId) .build(); - + this.setData(proto); } + + public PacketLifeStateChangeNotify(GameEntity entity, LifeState lifeState, PlayerDieType dieType) { + this(entity, lifeState, null, "", dieType); + } + + public PacketLifeStateChangeNotify(GameEntity entity, LifeState lifeState, GameEntity sourceEntity, + String attackTag, PlayerDieType dieType) { + super(PacketOpcodes.LifeStateChangeNotify); + + LifeStateChangeNotify.Builder proto = LifeStateChangeNotify.newBuilder(); + + + proto.setEntityId(entity.getId()); + proto.setLifeState(lifeState.getValue()); + if (sourceEntity != null) { + proto.setSourceEntityId(sourceEntity.getId()); + } + proto.setAttackTag(attackTag); + proto.setDieType(dieType); + + this.setData(proto.build()); + } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerReviveRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerReviveRsp.java new file mode 100644 index 000000000..ff93d2b00 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerReviveRsp.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.world.World; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WorldPlayerReviveRspOuterClass.WorldPlayerReviveRsp; + +public class PacketWorldPlayerReviveRsp extends BasePacket { + + public PacketWorldPlayerReviveRsp() { + super(PacketOpcodes.WorldPlayerReviveRsp); + + WorldPlayerReviveRsp.Builder proto = WorldPlayerReviveRsp.newBuilder(); + + this.setData(proto.build()); + } +}