From 744aa478a979c3ec21b1b6f715eccb618de3fb15 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Thu, 5 May 2022 22:07:29 -0700 Subject: [PATCH 1/9] Add drowning. Better movement ticking. --- proto/WorldPlayerReviveReq.proto | 15 + .../grasscutter/game/entity/EntityAvatar.java | 5 + .../managers/MotionManager/MotionManager.java | 227 ----------- .../MovementManager/MovementManager.java | 359 ++++++++++++++++++ .../emu/grasscutter/game/player/Player.java | 34 +- .../grasscutter/game/player/TeamManager.java | 63 +-- .../recv/HandlerCombatInvocationsNotify.java | 15 +- .../recv/HandlerWorldPlayerReviveReq.java | 3 + .../PacketAvatarLifeStateChangeNotify.java | 28 +- .../send/PacketLifeStateChangeNotify.java | 29 +- .../send/PacketWorldPlayerReviveRsp.java | 18 + 11 files changed, 500 insertions(+), 296 deletions(-) create mode 100644 proto/WorldPlayerReviveReq.proto delete mode 100644 src/main/java/emu/grasscutter/game/managers/MotionManager/MotionManager.java create mode 100644 src/main/java/emu/grasscutter/game/managers/MovementManager/MovementManager.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketWorldPlayerReviveRsp.java diff --git a/proto/WorldPlayerReviveReq.proto b/proto/WorldPlayerReviveReq.proto new file mode 100644 index 000000000..edaeab683 --- /dev/null +++ b/proto/WorldPlayerReviveReq.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +option csharp_namespace = "YSFreedom.Common.Protocol"; + + +message WorldPlayerReviveReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 288; + } + +} 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()); + } +} From 696f6290804a81e7aa434f4c410df8d97222c58a Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Fri, 6 May 2022 14:10:23 +0800 Subject: [PATCH 2/9] Choose Avatar & Enter Tower --- proto/TowerBuffSelectReq.proto | 16 ++++ proto/TowerBuffSelectRsp.proto | 16 ++++ proto/TowerCurLevelRecordChangeNotify.proto | 17 +++++ proto/TowerEnterLevelReq.proto | 16 ++++ proto/TowerEnterLevelRsp.proto | 18 +++++ proto/TowerLevelStarCondData.proto | 9 +++ proto/TowerLevelStarCondNotify.proto | 19 +++++ proto/TowerTeamSelectReq.proto | 19 +++++ proto/TowerTeamSelectRsp.proto | 15 ++++ .../java/emu/grasscutter/data/GameData.java | 11 ++- .../grasscutter/data/def/TowerFloorData.java | 73 +++++++++++++++++++ .../grasscutter/data/def/TowerLevelData.java | 55 ++++++++++++++ .../game/dungeons/DungeonManager.java | 3 +- .../emu/grasscutter/game/player/Player.java | 11 +++ .../emu/grasscutter/game/player/TeamInfo.java | 5 ++ .../grasscutter/game/player/TeamManager.java | 66 ++++++++++++++--- .../grasscutter/game/tower/TowerManager.java | 40 ++++++++++ .../recv/HandlerTowerEnterLevelReq.java | 21 ++++++ .../recv/HandlerTowerTeamSelectReq.java | 26 +++++++ .../packet/send/PacketTowerAllDataRsp.java | 13 +++- .../packet/send/PacketTowerEnterLevelRsp.java | 22 ++++++ .../packet/send/PacketTowerTeamSelectRsp.java | 17 +++++ 22 files changed, 495 insertions(+), 13 deletions(-) create mode 100644 proto/TowerBuffSelectReq.proto create mode 100644 proto/TowerBuffSelectRsp.proto create mode 100644 proto/TowerCurLevelRecordChangeNotify.proto create mode 100644 proto/TowerEnterLevelReq.proto create mode 100644 proto/TowerEnterLevelRsp.proto create mode 100644 proto/TowerLevelStarCondData.proto create mode 100644 proto/TowerLevelStarCondNotify.proto create mode 100644 proto/TowerTeamSelectReq.proto create mode 100644 proto/TowerTeamSelectRsp.proto create mode 100644 src/main/java/emu/grasscutter/data/def/TowerFloorData.java create mode 100644 src/main/java/emu/grasscutter/data/def/TowerLevelData.java create mode 100644 src/main/java/emu/grasscutter/game/tower/TowerManager.java create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerEnterLevelReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerTeamSelectReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketTowerEnterLevelRsp.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketTowerTeamSelectRsp.java diff --git a/proto/TowerBuffSelectReq.proto b/proto/TowerBuffSelectReq.proto new file mode 100644 index 000000000..1641b350a --- /dev/null +++ b/proto/TowerBuffSelectReq.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message TowerBuffSelectReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 2424; + } + + uint32 tower_buff_id = 1; +} diff --git a/proto/TowerBuffSelectRsp.proto b/proto/TowerBuffSelectRsp.proto new file mode 100644 index 000000000..7a32a8e1e --- /dev/null +++ b/proto/TowerBuffSelectRsp.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message TowerBuffSelectRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 2491; + } + + int32 retcode = 1; + uint32 tower_buff_id = 2; +} diff --git a/proto/TowerCurLevelRecordChangeNotify.proto b/proto/TowerCurLevelRecordChangeNotify.proto new file mode 100644 index 000000000..fd9f94c97 --- /dev/null +++ b/proto/TowerCurLevelRecordChangeNotify.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "TowerCurLevelRecord.proto"; + +message TowerCurLevelRecordChangeNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 2489; + } + + TowerCurLevelRecord cur_level_record = 1; +} diff --git a/proto/TowerEnterLevelReq.proto b/proto/TowerEnterLevelReq.proto new file mode 100644 index 000000000..551f47729 --- /dev/null +++ b/proto/TowerEnterLevelReq.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message TowerEnterLevelReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 2412; + } + + uint32 enter_point_id = 1; +} diff --git a/proto/TowerEnterLevelRsp.proto b/proto/TowerEnterLevelRsp.proto new file mode 100644 index 000000000..fbcc4067f --- /dev/null +++ b/proto/TowerEnterLevelRsp.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message TowerEnterLevelRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 2426; + } + + int32 retcode = 1; + uint32 floor_id = 2; + uint32 level_index = 3; + repeated uint32 tower_buff_id_list = 4; +} diff --git a/proto/TowerLevelStarCondData.proto b/proto/TowerLevelStarCondData.proto new file mode 100644 index 000000000..d46334422 --- /dev/null +++ b/proto/TowerLevelStarCondData.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message TowerLevelStarCondData { + uint32 star_cond_index = 3; + uint32 cond_value = 4; + bool is_pause = 5; +} diff --git a/proto/TowerLevelStarCondNotify.proto b/proto/TowerLevelStarCondNotify.proto new file mode 100644 index 000000000..e605496fb --- /dev/null +++ b/proto/TowerLevelStarCondNotify.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "TowerLevelStarCondData.proto"; + +message TowerLevelStarCondNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 2492; + } + + uint32 floor_id = 1; + uint32 level_index = 2; + repeated TowerLevelStarCondData cond_data_list = 3; +} diff --git a/proto/TowerTeamSelectReq.proto b/proto/TowerTeamSelectReq.proto new file mode 100644 index 000000000..903968061 --- /dev/null +++ b/proto/TowerTeamSelectReq.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "TowerTeam.proto"; + +message TowerTeamSelectReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 2401; + } + + uint32 floor_id = 1; + repeated TowerTeam tower_team_list = 2; +} diff --git a/proto/TowerTeamSelectRsp.proto b/proto/TowerTeamSelectRsp.proto new file mode 100644 index 000000000..b135e5364 --- /dev/null +++ b/proto/TowerTeamSelectRsp.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message TowerTeamSelectRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 2494; + } + + int32 retcode = 1; +} diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 692427496..76a7f1652 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -68,7 +68,9 @@ public class GameData { private static final Int2ObjectMap shopGoodsDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap combineDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap rewardPreviewDataMap = new Int2ObjectOpenHashMap<>(); - + private static final Int2ObjectMap towerFloorDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap towerLevelDataMap = new Int2ObjectOpenHashMap<>(); + // Cache private static Map> fetters = new HashMap<>(); private static Map> shopGoods = new HashMap<>(); @@ -311,4 +313,11 @@ public class GameData { public static Int2ObjectMap getCombineDataMap() { return combineDataMap; } + + public static Int2ObjectMap getTowerFloorDataMap(){ + return towerFloorDataMap; + } + public static Int2ObjectMap getTowerLevelDataMap(){ + return towerLevelDataMap; + } } diff --git a/src/main/java/emu/grasscutter/data/def/TowerFloorData.java b/src/main/java/emu/grasscutter/data/def/TowerFloorData.java new file mode 100644 index 000000000..d9d0082c7 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/TowerFloorData.java @@ -0,0 +1,73 @@ +package emu.grasscutter.data.def; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; + +@ResourceType(name = "TowerFloorExcelConfigData.json") +public class TowerFloorData extends GameResource { + + private int FloorId; + private int FloorIndex; + private int LevelId; + private int OverrideMonsterLevel; + private int TeamNum; + private int FloorLevelConfigId; + + @Override + public int getId() { + return this.FloorId; + } + + @Override + public void onLoad() { + super.onLoad(); + } + + public int getFloorId() { + return FloorId; + } + + public void setFloorId(int floorId) { + FloorId = floorId; + } + + public int getFloorIndex() { + return FloorIndex; + } + + public void setFloorIndex(int floorIndex) { + FloorIndex = floorIndex; + } + + public int getLevelId() { + return LevelId; + } + + public void setLevelId(int levelId) { + LevelId = levelId; + } + + public int getOverrideMonsterLevel() { + return OverrideMonsterLevel; + } + + public void setOverrideMonsterLevel(int overrideMonsterLevel) { + OverrideMonsterLevel = overrideMonsterLevel; + } + + public int getTeamNum() { + return TeamNum; + } + + public void setTeamNum(int teamNum) { + TeamNum = teamNum; + } + + public int getFloorLevelConfigId() { + return FloorLevelConfigId; + } + + public void setFloorLevelConfigId(int floorLevelConfigId) { + FloorLevelConfigId = floorLevelConfigId; + } +} diff --git a/src/main/java/emu/grasscutter/data/def/TowerLevelData.java b/src/main/java/emu/grasscutter/data/def/TowerLevelData.java new file mode 100644 index 000000000..6cc45cc06 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/TowerLevelData.java @@ -0,0 +1,55 @@ +package emu.grasscutter.data.def; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; + +@ResourceType(name = "TowerLevelExcelConfigData.json") +public class TowerLevelData extends GameResource { + + private int ID; + private int LevelId; + private int LevelIndex; + private int DungeonId; + + @Override + public int getId() { + return this.ID; + } + + @Override + public void onLoad() { + super.onLoad(); + } + + public int getID() { + return ID; + } + + public void setID(int ID) { + this.ID = ID; + } + + public int getLevelId() { + return LevelId; + } + + public void setLevelId(int levelId) { + LevelId = levelId; + } + + public int getLevelIndex() { + return LevelIndex; + } + + public void setLevelIndex(int levelIndex) { + LevelIndex = levelIndex; + } + + public int getDungeonId() { + return DungeonId; + } + + public void setDungeonId(int dungeonId) { + DungeonId = dungeonId; + } +} diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java index c84ef8a22..e858decf8 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java @@ -75,7 +75,8 @@ public class DungeonManager { prevPos.set(entry.getPointData().getTranPos()); } } - + // clean temp team if it has + player.getTeamManager().cleanTemporaryTeam(); // Transfer player back to world player.getWorld().transferPlayerToScene(player, prevScene, prevPos); player.sendPacket(new BasePacket(PacketOpcodes.PlayerQuitDungeonRsp)); diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index f6ea68dc6..d864f9c34 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -27,6 +27,7 @@ import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.shop.ShopLimit; import emu.grasscutter.game.managers.MapMarkManager.*; +import emu.grasscutter.game.tower.TowerManager; import emu.grasscutter.game.world.Scene; import emu.grasscutter.game.world.World; import emu.grasscutter.net.packet.BasePacket; @@ -89,6 +90,8 @@ public class Player { @Transient private MessageHandler messageHandler; private TeamManager teamManager; + + private TowerManager towerManager; private PlayerGachaInfo gachaInfo; private PlayerProfile playerProfile; private boolean showAvatar; @@ -176,6 +179,7 @@ public class Player { this.nickname = "Traveler"; this.signature = ""; this.teamManager = new TeamManager(this); + this.towerManager = new TowerManager(this); this.birthday = new PlayerBirthday(); this.setProperty(PlayerProperty.PROP_PLAYER_LEVEL, 1); this.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, 1); @@ -389,6 +393,10 @@ public class Player { return this.teamManager; } + public TowerManager getTowerManager() { + return towerManager; + } + public PlayerGachaInfo getGachaInfo() { return gachaInfo; } @@ -1030,6 +1038,9 @@ public class Player { if (this.getProfile().getUid() == 0) { this.getProfile().syncWithCharacter(this); } + if (this.getTowerManager() == null) { + this.towerManager = new TowerManager(this); + } // Check if player object exists in server // TODO - optimize diff --git a/src/main/java/emu/grasscutter/game/player/TeamInfo.java b/src/main/java/emu/grasscutter/game/player/TeamInfo.java index 5c66f1aaa..5794a7913 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamInfo.java +++ b/src/main/java/emu/grasscutter/game/player/TeamInfo.java @@ -18,6 +18,11 @@ public class TeamInfo { this.avatars = new ArrayList<>(Grasscutter.getConfig().getGameServerOptions().MaxAvatarsInTeam); } + public TeamInfo(List avatars) { + this.name = ""; + this.avatars = avatars; + } + public String getName() { return name; } diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index ff9a3c68d..30418993e 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -1,12 +1,6 @@ package emu.grasscutter.game.player; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import dev.morphia.annotations.Entity; import dev.morphia.annotations.Transient; @@ -59,7 +53,13 @@ public class TeamManager { @Transient private final Set gadgets; @Transient private final IntSet teamResonances; @Transient private final IntSet teamResonancesConfig; - + + private int useTemporarilyTeamIndex = -1; + /** + * Temporary Team for tower + */ + private List temporaryTeam; + public TeamManager() { this.mpTeam = new TeamInfo(); this.avatars = new ArrayList<>(); @@ -125,6 +125,10 @@ public class TeamManager { } public TeamInfo getCurrentTeamInfo() { + if (useTemporarilyTeamIndex >= 0 && + useTemporarilyTeamIndex < temporaryTeam.size()){ + return temporaryTeam.get(useTemporarilyTeamIndex); + } if (this.getPlayer().isInMultiplayer()) { return this.getMpTeam(); } @@ -352,7 +356,51 @@ public class TeamManager { // Packet this.updateTeamEntities(new PacketChangeMpTeamAvatarRsp(getPlayer(), teamInfo)); } - + + public void setupTemporaryTeam(List> guidList) { + var team = guidList.stream().map(list -> { + // Sanity checks + if (list.size() == 0 || list.size() > getMaxTeamSize()) { + return null; + } + + // Set team data + LinkedHashSet newTeam = new LinkedHashSet<>(); + for (int i = 0; i < list.size(); i++) { + Avatar avatar = getPlayer().getAvatars().getAvatarByGuid(list.get(i)); + if (avatar == null || newTeam.contains(avatar)) { + // Should never happen + return null; + } + newTeam.add(avatar); + } + + // convert to avatar ids + return newTeam.stream() + .map(Avatar::getAvatarId) + .toList(); + }) + .filter(Objects::nonNull) + .map(TeamInfo::new) + .toList(); + this.temporaryTeam = team; + } + + public void useTemporaryTeam(int index) { + this.useTemporarilyTeamIndex = index; + updateTeamEntities(null); + } + + public void cleanTemporaryTeam() { + // check if using temporary team + if(useTemporarilyTeamIndex < 0){ + return; + } + + this.useTemporarilyTeamIndex = -1; + this.temporaryTeam = null; + updateTeamEntities(null); + } public synchronized void setCurrentTeam(int teamId) { // if (getPlayer().isInMultiplayer()) { diff --git a/src/main/java/emu/grasscutter/game/tower/TowerManager.java b/src/main/java/emu/grasscutter/game/tower/TowerManager.java new file mode 100644 index 000000000..e49a15cc2 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/tower/TowerManager.java @@ -0,0 +1,40 @@ +package emu.grasscutter.game.tower; + +import dev.morphia.annotations.Entity; +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.server.packet.send.PacketTowerEnterLevelRsp; + +import java.util.List; + +@Entity +public class TowerManager { + private final Player player; + + public TowerManager(Player player) { + this.player = player; + } + private int currentLevel; + private int currentFloor; + + public void teamSelect(int floor, List> towerTeams) { + var floorData = GameData.getTowerFloorDataMap().get(floor); + + this.currentFloor = floorData.getFloorId(); + this.currentLevel = floorData.getLevelId(); + + player.getTeamManager().setupTemporaryTeam(towerTeams); + } + + + public void enterLevel(int enterPointId) { + var levelData = GameData.getTowerLevelDataMap().get(currentLevel); + var id = levelData.getDungeonId(); + // use team user choose + player.getTeamManager().useTemporaryTeam(0); + player.getServer().getDungeonManager() + .enterDungeon(player, enterPointId, id); + + player.getSession().send(new PacketTowerEnterLevelRsp(currentFloor, currentLevel)); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerEnterLevelReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerEnterLevelReq.java new file mode 100644 index 000000000..163f101ed --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerEnterLevelReq.java @@ -0,0 +1,21 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TowerEnterLevelReqOuterClass.TowerEnterLevelReq; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.TowerEnterLevelReq) +public class HandlerTowerEnterLevelReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + TowerEnterLevelReq req = TowerEnterLevelReq.parseFrom(payload); + + //session.send(new PacketTowerCurLevelRecordChangeNotify()); + session.getPlayer().getTowerManager().enterLevel(req.getEnterPointId()); + + //session.send(new PacketTowerLevelStarCondNotify()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerTeamSelectReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerTeamSelectReq.java new file mode 100644 index 000000000..6e6705379 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTowerTeamSelectReq.java @@ -0,0 +1,26 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TowerTeamOuterClass; +import emu.grasscutter.net.proto.TowerTeamSelectReqOuterClass.TowerTeamSelectReq; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketTowerTeamSelectRsp; + +@Opcodes(PacketOpcodes.TowerTeamSelectReq) +public class HandlerTowerTeamSelectReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + TowerTeamSelectReq req = TowerTeamSelectReq.parseFrom(payload); + + var towerTeams = req.getTowerTeamListList().stream() + .map(TowerTeamOuterClass.TowerTeam::getAvatarGuidListList) + .toList(); + + session.getPlayer().getTowerManager().teamSelect(req.getFloorId(), towerTeams); + + session.send(new PacketTowerTeamSelectRsp()); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java index 2bd1d0171..d2d2376e6 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerAllDataRsp.java @@ -1,19 +1,28 @@ package emu.grasscutter.server.packet.send; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.TowerFloorData; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.TowerAllDataRspOuterClass.TowerAllDataRsp; import emu.grasscutter.net.proto.TowerCurLevelRecordOuterClass.TowerCurLevelRecord; import emu.grasscutter.net.proto.TowerFloorRecordOuterClass.TowerFloorRecord; +import java.util.stream.Collectors; + public class PacketTowerAllDataRsp extends BasePacket { public PacketTowerAllDataRsp() { super(PacketOpcodes.TowerAllDataRsp); - + + var list = GameData.getTowerFloorDataMap().values().stream() + .map(TowerFloorData::getFloorId) + .map(id -> TowerFloorRecord.newBuilder().setFloorId(id).build()) + .collect(Collectors.toList()); + TowerAllDataRsp proto = TowerAllDataRsp.newBuilder() .setTowerScheduleId(29) - .addTowerFloorRecordList(TowerFloorRecord.newBuilder().setFloorId(1001)) + .addAllTowerFloorRecordList(list) .setCurLevelRecord(TowerCurLevelRecord.newBuilder().setIsEmpty(true)) .setNextScheduleChangeTime(Integer.MAX_VALUE) .putFloorOpenTimeMap(1024, 1630486800) diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerEnterLevelRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerEnterLevelRsp.java new file mode 100644 index 000000000..ebb8fb2b2 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerEnterLevelRsp.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TowerEnterLevelRspOuterClass.TowerEnterLevelRsp; + +public class PacketTowerEnterLevelRsp extends BasePacket { + + public PacketTowerEnterLevelRsp(int floorId, int levelIndex) { + super(PacketOpcodes.TowerEnterLevelRsp); + + TowerEnterLevelRsp proto = TowerEnterLevelRsp.newBuilder() + .setFloorId(floorId) + .setLevelIndex(levelIndex) + .addTowerBuffIdList(4) + .addTowerBuffIdList(28) + .addTowerBuffIdList(18) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerTeamSelectRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerTeamSelectRsp.java new file mode 100644 index 000000000..445b707cd --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerTeamSelectRsp.java @@ -0,0 +1,17 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TowerTeamSelectRspOuterClass.TowerTeamSelectRsp; + +public class PacketTowerTeamSelectRsp extends BasePacket { + + public PacketTowerTeamSelectRsp() { + super(PacketOpcodes.TowerTeamSelectRsp); + + TowerTeamSelectRsp proto = TowerTeamSelectRsp.newBuilder() + .build(); + + this.setData(proto); + } +} From 39a49ae9643ae047b39b5a856bfd717aa54834a9 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Fri, 6 May 2022 14:46:10 +0800 Subject: [PATCH 3/9] Add @Transient for temporary team --- src/main/java/emu/grasscutter/game/player/TeamManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index 30418993e..16e8942ad 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -54,11 +54,11 @@ public class TeamManager { @Transient private final IntSet teamResonances; @Transient private final IntSet teamResonancesConfig; - private int useTemporarilyTeamIndex = -1; + @Transient private int useTemporarilyTeamIndex = -1; /** * Temporary Team for tower */ - private List temporaryTeam; + @Transient private List temporaryTeam; public TeamManager() { this.mpTeam = new TeamInfo(); From 39c932b041e2e9fd18d305b9936fb3103f6a2f90 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Fri, 6 May 2022 00:05:38 -0700 Subject: [PATCH 4/9] Implementes auto HP recovery at the statues. - Respects player setting. - SP + MP. - Statue has unlimited HP volume (to be updated) --- .../managers/SotSManager/SotSManager.java | 118 ++++++++++++++++++ .../emu/grasscutter/game/player/Player.java | 7 ++ .../HandlerEnterTransPointRegionNotify.java | 28 ++--- 3 files changed, 132 insertions(+), 21 deletions(-) create mode 100644 src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java diff --git a/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java b/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java new file mode 100644 index 000000000..b734f5908 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java @@ -0,0 +1,118 @@ +package emu.grasscutter.game.managers.SotSManager; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.FightProperty; +import emu.grasscutter.game.props.PlayerProperty; +import emu.grasscutter.net.proto.ChangeHpReasonOuterClass; +import emu.grasscutter.net.proto.PropChangeReasonOuterClass; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify; +import emu.grasscutter.server.packet.send.PacketAvatarLifeStateChangeNotify; +import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify; +import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; + +import java.util.List; + +// Statue of the Seven Manager +public class SotSManager { + + private final Player player; + + public SotSManager(Player player) { + this.player = player; + } + + public boolean getIsAutoRecoveryEnabled() { + return player.getProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE) == 1; + } + + public void setIsAutoRecoveryEnabled(boolean enabled) { + player.setProperty(PlayerProperty.PROP_IS_SPRING_AUTO_USE, enabled ? 1 : 0); + } + + public int getAutoRecoveryPercentage() { + return player.getProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT); + } + + public void setAutoRecoveryPercentage(int percentage) { + player.setProperty(PlayerProperty.PROP_SPRING_AUTO_USE_PERCENT, percentage); + } + + // autoRevive automatically revives all team members. + public void autoRevive(GameSession session) { + player.getTeamManager().getActiveTeam().forEach(entity -> { + boolean isAlive = entity.isAlive(); + if (!isAlive) { + float maxHP = entity.getAvatar().getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, Math.min(150, maxHP)); + entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); + } + }); + } + + public void scheduleAutoRecover(GameSession session) { + // TODO: play audio effects? possibly client side? - client automatically plays. + // delay 2.5 seconds + new Thread(() -> { + try { + Thread.sleep(2500); + autoRecover(session); + } catch (Exception e) { + Grasscutter.getLogger().error(e.getMessage()); + } + }).start(); + } + + // autoRecover checks player setting to see if auto recover is enabled, and refill HP to the predefined level. + public void autoRecover(GameSession session) { + // TODO: Implement SotS Spring volume refill over time. + // Placeholder: + player.setProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME, 8500000); + player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, 8500000); + + // TODO: In MP, respect SotS settings from the host. + boolean isAutoRecoveryEnabled = getIsAutoRecoveryEnabled(); + int autoRecoverPercentage = getAutoRecoveryPercentage(); + Grasscutter.getLogger().warn("isAutoRecoveryEnabled: " + isAutoRecoveryEnabled + "\tautoRecoverPercentage: " + autoRecoverPercentage); + + if (isAutoRecoveryEnabled) { + player.getTeamManager().getActiveTeam().forEach(entity -> { + float maxHP = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + float currentHP = entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + if (currentHP == maxHP) { + return; + } + float targetHP = maxHP * autoRecoverPercentage / 100; + + if (targetHP > currentHP) { + float needHP = targetHP - currentHP; + + int sotsHPBalance = player.getProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME); + if (sotsHPBalance >= needHP) { + sotsHPBalance -= needHP; + player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, sotsHPBalance); + + float newHP = currentHP + needHP; + + session.send(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_MAX_HP)); + session.send(new PacketEntityFightPropChangeReasonNotify(entity, FightProperty.FIGHT_PROP_CUR_HP, + newHP, List.of(3), PropChangeReasonOuterClass.PropChangeReason.PROP_CHANGE_STATUE_RECOVER, + ChangeHpReasonOuterClass.ChangeHpReason.ChangeHpAddStatue)); + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); + + Avatar avatar = entity.getAvatar(); + session.send(new PacketAvatarFightPropUpdateNotify(avatar, FightProperty.FIGHT_PROP_CUR_HP)); + avatar.setCurrentHp(newHP); + + player.save(); + } + + } + }); + } + } + + +} diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index d864f9c34..eb45f1390 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -22,6 +22,7 @@ import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.mail.MailHandler; import emu.grasscutter.game.managers.MovementManager.MovementManager; +import emu.grasscutter.game.managers.SotSManager.SotSManager; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.PlayerProperty; @@ -89,6 +90,8 @@ public class Player { @Transient private MailHandler mailHandler; @Transient private MessageHandler messageHandler; + @Transient private SotSManager sotsManager; + private TeamManager teamManager; private TowerManager towerManager; @@ -168,6 +171,7 @@ public class Player { this.messageHandler = null; this.mapMarksManager = new MapMarksManager(); this.movementManager = new MovementManager(this); + this.sotsManager = new SotSManager(this); } // On player creation @@ -196,6 +200,7 @@ public class Player { this.messageHandler = null; this.mapMarksManager = new MapMarksManager(); this.movementManager = new MovementManager(this); + this.sotsManager = new SotSManager(this); } public int getUid() { @@ -984,6 +989,8 @@ public class Player { public MovementManager getMovementManager() { return movementManager; } + public SotSManager getSotSManager() { return sotsManager; } + public synchronized void onTick() { // Check ping if (this.getLastPingTime() > System.currentTimeMillis() + 60000) { diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java index 2c946e1fa..0ec0a844f 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java @@ -1,5 +1,7 @@ package emu.grasscutter.server.packet.recv; +import emu.grasscutter.game.managers.SotSManager.SotSManager; +import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketHandler; @@ -18,26 +20,10 @@ import java.util.List; public class HandlerEnterTransPointRegionNotify extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception{ - session.getPlayer().getTeamManager().getActiveTeam().forEach(entity -> { - boolean isAlive = entity.isAlive(); - if(entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) != entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP)){ - Float hp = entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP)-entity.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); - - session.send(new PacketEntityFightPropUpdateNotify(entity,FightProperty.FIGHT_PROP_MAX_HP)); - - session.send(new PacketEntityFightPropChangeReasonNotify( - entity, FightProperty.FIGHT_PROP_CUR_HP, hp, List.of(3), - PropChangeReason.PROP_CHANGE_STATUE_RECOVER, ChangeHpReason.ChangeHpAddStatue)); - - entity.setFightProperty( - FightProperty.FIGHT_PROP_CUR_HP, - entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) - ); - session.send(new PacketAvatarFightPropUpdateNotify(entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP)); - if (!isAlive) { - entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); - } - } - }); + Player player = session.getPlayer(); + SotSManager sotsManager = player.getSotSManager(); + sotsManager.autoRevive(session); + sotsManager.scheduleAutoRecover(session); + // TODO: allow interaction with the SotS? } } From e319fd751bd2e79e79345c22e4bf994b89daf68b Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Fri, 6 May 2022 00:28:35 -0700 Subject: [PATCH 5/9] fix: lower logging level in SotSManager --- .../emu/grasscutter/game/managers/SotSManager/SotSManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java b/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java index b734f5908..dec54f686 100644 --- a/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java +++ b/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java @@ -75,7 +75,7 @@ public class SotSManager { // TODO: In MP, respect SotS settings from the host. boolean isAutoRecoveryEnabled = getIsAutoRecoveryEnabled(); int autoRecoverPercentage = getAutoRecoveryPercentage(); - Grasscutter.getLogger().warn("isAutoRecoveryEnabled: " + isAutoRecoveryEnabled + "\tautoRecoverPercentage: " + autoRecoverPercentage); + Grasscutter.getLogger().debug("isAutoRecoveryEnabled: " + isAutoRecoveryEnabled + "\tautoRecoverPercentage: " + autoRecoverPercentage); if (isAutoRecoveryEnabled) { player.getTeamManager().getActiveTeam().forEach(entity -> { From 5968ed3a71d147caab5fa4aa768ff2e5de0c2eef Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 6 May 2022 01:17:16 -0700 Subject: [PATCH 6/9] Remove the red exclamation mark from achievements --- .../packet/send/PacketTakeAchievementRewardReq.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTakeAchievementRewardReq.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTakeAchievementRewardReq.java index 24135d52c..66049c64c 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketTakeAchievementRewardReq.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTakeAchievementRewardReq.java @@ -15,14 +15,7 @@ public class PacketTakeAchievementRewardReq extends BasePacket { public PacketTakeAchievementRewardReq(GameSession session) { super(PacketOpcodes.TakeAchievementRewardReq); - List a_list = new ArrayList<>(); - a_list.add(AchievementInfo.newBuilder().setId(82044).setStatusValue(2).setCurrent(0).setGoal(1).build()); - a_list.add(AchievementInfo.newBuilder().setId(81205).setStatusValue(2).setCurrent(0).setGoal(1).build()); - - - TakeAchievementRewardReq proto = TakeAchievementRewardReq.newBuilder() - .addAllAList(a_list) - .build(); + TakeAchievementRewardReq proto = TakeAchievementRewardReq.newBuilder().build(); this.setData(proto); } From 098cf372c91492e98da050bcb67071321d7bbe53 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 6 May 2022 01:19:39 -0700 Subject: [PATCH 7/9] Fix morphia error when saving player to db --- src/main/java/emu/grasscutter/game/tower/TowerManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/game/tower/TowerManager.java b/src/main/java/emu/grasscutter/game/tower/TowerManager.java index e49a15cc2..3b45785dd 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerManager.java @@ -1,6 +1,7 @@ package emu.grasscutter.game.tower; import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Transient; import emu.grasscutter.data.GameData; import emu.grasscutter.game.player.Player; import emu.grasscutter.server.packet.send.PacketTowerEnterLevelRsp; @@ -9,11 +10,12 @@ import java.util.List; @Entity public class TowerManager { - private final Player player; + @Transient private final Player player; public TowerManager(Player player) { this.player = player; } + private int currentLevel; private int currentFloor; From 0102a3ce1e83182f0ef04380155c6541ee3cfa0b Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Fri, 6 May 2022 02:23:10 -0700 Subject: [PATCH 8/9] The statues will now automatically regen their HP volume over time. Max is currently set to 85000 for everyone. Will update after implementing statue levels. --- .../managers/SotSManager/SotSManager.java | 79 +++++++++++++------ .../emu/grasscutter/game/player/Player.java | 10 +++ .../HandlerEnterTransPointRegionNotify.java | 2 + 3 files changed, 66 insertions(+), 25 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java b/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java index dec54f686..4edad231e 100644 --- a/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java +++ b/src/main/java/emu/grasscutter/game/managers/SotSManager/SotSManager.java @@ -18,6 +18,8 @@ import java.util.List; // Statue of the Seven Manager public class SotSManager { + // NOTE: Spring volume balance *1 = fight prop HP *100 + private final Player player; public SotSManager(Player player) { @@ -46,7 +48,8 @@ public class SotSManager { boolean isAlive = entity.isAlive(); if (!isAlive) { float maxHP = entity.getAvatar().getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); - entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, Math.min(150, maxHP)); + float newHP = (float)(maxHP * 0.3); + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); entity.getWorld().broadcastPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); } }); @@ -65,14 +68,31 @@ public class SotSManager { }).start(); } + public void refillSpringVolume() { + // TODO: max spring volume depends on level of the statues in Mondstadt and Liyue. + // https://genshin-impact.fandom.com/wiki/Statue_of_The_Seven#:~:text=region%20of%20Inazuma.-,Statue%20Levels,-Upon%20first%20unlocking + player.setProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME, 8500000); + + long now = System.currentTimeMillis() / 1000; + long secondsSinceLastUsed = now - player.getSpringLastUsed(); + float percentageRefilled = (float)secondsSinceLastUsed / 15 / 100; // 15s = 1% max volume + int maxVolume = player.getProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME); + int currentVolume = player.getProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME); + if (currentVolume < maxVolume) { + int volumeRefilled = (int)(percentageRefilled * maxVolume); + int newVolume = currentVolume + volumeRefilled; + if (currentVolume + volumeRefilled > maxVolume) { + newVolume = maxVolume; + } + player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, newVolume); + } + player.setSpringLastUsed(now); + player.save(); + } + // autoRecover checks player setting to see if auto recover is enabled, and refill HP to the predefined level. public void autoRecover(GameSession session) { - // TODO: Implement SotS Spring volume refill over time. - // Placeholder: - player.setProperty(PlayerProperty.PROP_MAX_SPRING_VOLUME, 8500000); - player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, 8500000); - - // TODO: In MP, respect SotS settings from the host. + // TODO: In MP, respect SotS settings from the HOST. boolean isAutoRecoveryEnabled = getIsAutoRecoveryEnabled(); int autoRecoverPercentage = getAutoRecoveryPercentage(); Grasscutter.getLogger().debug("isAutoRecoveryEnabled: " + isAutoRecoveryEnabled + "\tautoRecoverPercentage: " + autoRecoverPercentage); @@ -88,27 +108,36 @@ public class SotSManager { if (targetHP > currentHP) { float needHP = targetHP - currentHP; + float needSV = needHP * 100; // convert HP needed to Spring Volume needed - int sotsHPBalance = player.getProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME); - if (sotsHPBalance >= needHP) { - sotsHPBalance -= needHP; - player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, sotsHPBalance); - - float newHP = currentHP + needHP; - - session.send(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_MAX_HP)); - session.send(new PacketEntityFightPropChangeReasonNotify(entity, FightProperty.FIGHT_PROP_CUR_HP, - newHP, List.of(3), PropChangeReasonOuterClass.PropChangeReason.PROP_CHANGE_STATUE_RECOVER, - ChangeHpReasonOuterClass.ChangeHpReason.ChangeHpAddStatue)); - entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); - - Avatar avatar = entity.getAvatar(); - session.send(new PacketAvatarFightPropUpdateNotify(avatar, FightProperty.FIGHT_PROP_CUR_HP)); - avatar.setCurrentHp(newHP); - - player.save(); + int sotsSVBalance = player.getProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME); + if (sotsSVBalance >= needSV) { + // sufficient + sotsSVBalance -= needSV; + } else { + // insufficient balance + needSV = sotsSVBalance; + sotsSVBalance = 0; } + player.setProperty(PlayerProperty.PROP_CUR_SPRING_VOLUME, sotsSVBalance); + player.setSpringLastUsed(System.currentTimeMillis() / 1000); + float newHP = currentHP + needSV / 100; // convert SV to HP + + // TODO: Figure out why client shows current HP instead of added HP. + // Say an avatar had 12000 and now has 14000, it should show "2000". + // The client always show "+14000" which is incorrect. + + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, newHP); + session.send(new PacketEntityFightPropChangeReasonNotify(entity, FightProperty.FIGHT_PROP_CUR_HP, + newHP, List.of(3), PropChangeReasonOuterClass.PropChangeReason.PROP_CHANGE_STATUE_RECOVER, + ChangeHpReasonOuterClass.ChangeHpReason.ChangeHpAddStatue)); + session.send(new PacketEntityFightPropUpdateNotify(entity, FightProperty.FIGHT_PROP_CUR_HP)); + + Avatar avatar = entity.getAvatar(); + avatar.setCurrentHp(newHP); + session.send(new PacketAvatarFightPropUpdateNotify(avatar, FightProperty.FIGHT_PROP_CUR_HP)); + player.save(); } }); } diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index eb45f1390..b93baccf8 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -130,6 +130,8 @@ public class Player { private MapMarksManager mapMarksManager; @Transient private MovementManager movementManager; + private long springLastUsed; + @Deprecated @SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only! @@ -535,6 +537,14 @@ public class Player { } } + public long getSpringLastUsed() { + return springLastUsed; + } + + public void setSpringLastUsed(long val) { + springLastUsed = val; + } + public SceneLoadState getSceneLoadState() { return sceneState; } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java index 0ec0a844f..5591607fe 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterTransPointRegionNotify.java @@ -22,6 +22,8 @@ public class HandlerEnterTransPointRegionNotify extends PacketHandler { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception{ Player player = session.getPlayer(); SotSManager sotsManager = player.getSotSManager(); + + sotsManager.refillSpringVolume(); sotsManager.autoRevive(session); sotsManager.scheduleAutoRecover(session); // TODO: allow interaction with the SotS? From da99140d2066392d2b236acde854b38a2e133caa Mon Sep 17 00:00:00 2001 From: 4Benj_ Date: Fri, 6 May 2022 21:48:16 +0800 Subject: [PATCH 9/9] Stop WindSeedClientNotify and PlayerLuaShellNotify from being sent (#582) --- src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java | 5 +++++ src/main/java/emu/grasscutter/server/game/GameSession.java | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java b/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java index 4d9eb57e8..8e77504d6 100644 --- a/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java +++ b/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java @@ -1,5 +1,8 @@ package emu.grasscutter.net.packet; +import java.util.Arrays; +import java.util.List; + public class PacketOpcodes { // Empty public static final int NONE = 0; @@ -1566,4 +1569,6 @@ public class PacketOpcodes { public static final int UNKNOWN_43 = 8877; public static final int UNKNOWN_44 = 8983; public static final int UNKNOWN_45 = 943; + + public static final List BANNED_PACKETS = Arrays.asList(PacketOpcodes.WindSeedClientNotify, PacketOpcodes.PlayerLuaShellNotify); } diff --git a/src/main/java/emu/grasscutter/server/game/GameSession.java b/src/main/java/emu/grasscutter/server/game/GameSession.java index ff024b03b..b984baa0e 100644 --- a/src/main/java/emu/grasscutter/server/game/GameSession.java +++ b/src/main/java/emu/grasscutter/server/game/GameSession.java @@ -157,6 +157,12 @@ public class GameSession extends KcpChannel { Grasscutter.getLogger().warn("Tried to send packet with missing cmd id!"); return; } + + // DO NOT REMOVE (unless we find a way to validate code before sending to client which I don't think we can) + // Stop WindSeedClientNotify from being sent for security purposes. + if(PacketOpcodes.BANNED_PACKETS.contains(packet.getOpcode())) { + return; + } // Header if (packet.shouldBuildHeader()) {