diff --git a/src/main/java/emu/grasscutter/game/managers/MotionManager/MotionManager.java b/src/main/java/emu/grasscutter/game/managers/MotionManager/MotionManager.java new file mode 100644 index 000000000..d8d6f25b0 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/MotionManager/MotionManager.java @@ -0,0 +1,227 @@ +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/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index c76f46bf0..b1abda002 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -21,6 +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.props.ActionReason; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.PlayerProperty; @@ -121,6 +122,8 @@ public class Player { @Transient private final InvokeHandler clientAbilityInitFinishHandler; private MapMarksManager mapMarksManager; + @Transient private MotionManager motionManager; + @Deprecated @SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only! @@ -161,6 +164,7 @@ public class Player { this.shopLimit = new ArrayList<>(); this.messageHandler = null; this.mapMarksManager = new MapMarksManager(); + this.motionManager = new MotionManager(this); } // On player creation @@ -187,6 +191,7 @@ public class Player { this.getRotation().set(0, 307, 0); this.messageHandler = null; this.mapMarksManager = new MapMarksManager(); + this.motionManager = new MotionManager(this); } public int getUid() { @@ -969,6 +974,8 @@ public class Player { return mapMarksManager; } + public MotionManager getMotionManager() { return motionManager; } + public synchronized void onTick() { // Check ping if (this.getLastPingTime() > System.currentTimeMillis() + 60000) { @@ -997,8 +1004,35 @@ 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() { this.nextSendPlayerLocTime = System.currentTimeMillis() + 5000; } 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 2d0ceac8b..878997e22 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java @@ -1,8 +1,9 @@ package emu.grasscutter.server.packet.recv; -import emu.grasscutter.Grasscutter; import emu.grasscutter.game.entity.GameEntity; -import emu.grasscutter.game.props.PlayerProperty; +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,13 +12,9 @@ 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.net.proto.VectorOuterClass.Vector; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.*; -import java.util.Arrays; -import java.util.Collection; - @Opcodes(PacketOpcodes.CombatInvocationsNotify) public class HandlerCombatInvocationsNotify extends PacketHandler { @@ -35,7 +32,6 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { // Handle movement EntityMoveInfo moveInfo = EntityMoveInfo.parseFrom(entry.getCombatData()); GameEntity entity = session.getPlayer().getScene().getEntityById(moveInfo.getEntityId()); - MotionState state = moveInfo.getMotionInfo().getState(); if (entity != null) { //move entity.getPosition().set(moveInfo.getMotionInfo().getPos()); @@ -43,56 +39,7 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime()); entity.setLastMoveReliableSeq(moveInfo.getReliableSeq()); entity.setMotionState(moveInfo.getMotionInfo().getState()); - - if(Grasscutter.getConfig().OpenStamina){ - //consume stamina - int curStamina = session.getPlayer().getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); - int maxStamina = session.getPlayer().getProperty(PlayerProperty.PROP_MAX_STAMINA); - if (CONSUME_STAMINA_LIST.contains(state)) { - - //In the water exhausted stamina - - //Climbing the wall stays in place - - //Sprint in the water - if (state == MotionState.MOTION_SWIM_DASH) { - curStamina -= 700; - } - //wall jump - else if (state == MotionState.MOTION_CLIMB_JUMP) { - curStamina -= 2000; - } - //climb the wall slowly - else if (state == MotionState.MOTION_CLIMB) { - curStamina -= 800; - } - else if (state == MotionState.MOTION_DASH_BEFORE_SHAKE) { - curStamina -= 2500; - } - else { - curStamina -= 500; - } - - session.getPlayer().setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, curStamina); - session.send(new PacketPlayerPropNotify(session.getPlayer(), PlayerProperty.PROP_CUR_PERSIST_STAMINA)); - break; - } - //restore stamina - if (RESTORE_STAMINA_LIST.contains(state)) { - if(state == MotionState.MOTION_STANDBY) { - Vector speed = moveInfo.getMotionInfo().getSpeed(); - if(speed.getX() != 0 && speed.getZ() != 0 && speed.getY() != 0) { - break; - } - } - curStamina += 1000; - if (curStamina >= maxStamina) { - curStamina = maxStamina; - } - session.getPlayer().setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, curStamina); - session.send(new PacketPlayerPropNotify(session.getPlayer(), PlayerProperty.PROP_CUR_PERSIST_STAMINA)); - } - } + session.getPlayer().getMotionManager().handle(session, entity, moveInfo); } break; default: @@ -111,17 +58,5 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { } } - private static MotionState[] consumeStaminaTypes = new MotionState[]{ - MotionState.MOTION_CLIMB, MotionState.MOTION_CLIMB_JUMP, MotionState.MOTION_SWIM_DASH, - MotionState.MOTION_SWIM_MOVE, MotionState.MOTION_FLY, MotionState.MOTION_DASH, - MotionState.MOTION_DASH_BEFORE_SHAKE, MotionState.MOTION_FIGHT, MotionState.MOTION_JUMP_UP_WALL_FOR_STANDBY, - MotionState.MOTION_FLY_SLOW - }; - private static MotionState[] restoreStaminaTypes = new MotionState[]{ - MotionState.MOTION_STANDBY, MotionState.MOTION_RUN, MotionState.MOTION_WALK, - MotionState.MOTION_STANDBY_MOVE - }; - private static final Collection CONSUME_STAMINA_LIST = Arrays.asList(consumeStaminaTypes); - private static final Collection RESTORE_STAMINA_LIST = Arrays.asList(restoreStaminaTypes); } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java new file mode 100644 index 000000000..d1db944d2 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java @@ -0,0 +1,26 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.EvtDoSkillSuccNotifyOuterClass.EvtDoSkillSuccNotify; +import emu.grasscutter.server.game.GameSession; + +@Opcodes(PacketOpcodes.EvtDoSkillSuccNotify) +public class HandlerEvtDoSkillSuccNotify extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + EvtDoSkillSuccNotify notify = EvtDoSkillSuccNotify.parseFrom(payload); + // TODO: Will be used for deducting stamina for charged skills. + + int caster = notify.getCasterId(); + int skill = notify.getSkillId(); + + // Grasscutter.getLogger().warn(caster + "\t" + skill); + +// session.getPlayer().getScene().broadcastPacket(new PacketEvtAvatarStandUpNotify(notify)); + } + +}