diff --git a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java index 7f2b16485..3448aa4c2 100644 --- a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java +++ b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java @@ -17,43 +17,84 @@ import emu.grasscutter.net.proto.AbilityMetaReInitOverrideMapOuterClass.AbilityM import emu.grasscutter.net.proto.AbilityMixinCostStaminaOuterClass.AbilityMixinCostStamina; import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalarValueEntry; import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction; +import lombok.Getter; -public class AbilityManager extends BasePlayerManager { +public final class AbilityManager extends BasePlayerManager { HealAbilityManager healAbilityManager; + @Getter private boolean abilityInvulnerable = false; + public AbilityManager(Player player) { super(player); this.healAbilityManager = new HealAbilityManager(player); } public void onAbilityInvoke(AbilityInvokeEntry invoke) throws Exception { - healAbilityManager.healHandler(invoke); + this.healAbilityManager.healHandler(invoke); //Grasscutter.getLogger().info(invoke.getArgumentType() + " (" + invoke.getArgumentTypeValue() + "): " + Utils.bytesToHex(invoke.toByteArray())); switch (invoke.getArgumentType()) { - case ABILITY_INVOKE_ARGUMENT_META_OVERRIDE_PARAM: - handleOverrideParam(invoke); - break; - case ABILITY_INVOKE_ARGUMENT_META_REINIT_OVERRIDEMAP: - handleReinitOverrideMap(invoke); - break; - case ABILITY_INVOKE_ARGUMENT_META_MODIFIER_CHANGE: - handleModifierChange(invoke); - break; - case ABILITY_INVOKE_ARGUMENT_MIXIN_COST_STAMINA: - handleMixinCostStamina(invoke); - break; - case ABILITY_INVOKE_ARGUMENT_ACTION_GENERATE_ELEM_BALL: - handleGenerateElemBall(invoke); - break; - default: - break; + case ABILITY_INVOKE_ARGUMENT_META_OVERRIDE_PARAM -> this.handleOverrideParam(invoke); + case ABILITY_INVOKE_ARGUMENT_META_REINIT_OVERRIDEMAP -> this.handleReinitOverrideMap(invoke); + case ABILITY_INVOKE_ARGUMENT_META_MODIFIER_CHANGE -> this.handleModifierChange(invoke); + case ABILITY_INVOKE_ARGUMENT_MIXIN_COST_STAMINA -> this.handleMixinCostStamina(invoke); + case ABILITY_INVOKE_ARGUMENT_ACTION_GENERATE_ELEM_BALL -> this.handleGenerateElemBall(invoke); + default -> {} + } + } + + /** + * Invoked when a player starts a skill. + * @param player The player who started the skill. + * @param skillId The skill ID. + * @param casterId The caster ID. + */ + public void onSkillStart(Player player, int skillId, int casterId) { + // Check if the player matches this player. + if (player.getUid() != this.player.getUid()) { + return; } + // Check if the caster matches the player. + if(player.getTeamManager().getCurrentAvatarEntity().getId() != casterId) { + return; + } + + var skillData = GameData.getAvatarSkillDataMap().get(skillId); + if (skillData == null) { + return; + } + + // Check if the skill is an elemental burst. + if(skillData.getCostElemVal() <= 0) { + return; + } + + // Set the player as invulnerable. + this.abilityInvulnerable = true; + } + + /** + * Invoked when a player ends a skill. + * @param player The player who started the skill. + */ + public void onSkillEnd(Player player) { + // Check if the player matches this player. + if (player.getUid() != this.player.getUid()) { + return; + } + + // Check if the player is invulnerable. + if(!this.abilityInvulnerable) { + return; + } + + // Set the player as not invulnerable. + this.abilityInvulnerable = false; } private void handleOverrideParam(AbilityInvokeEntry invoke) throws Exception { - GameEntity entity = player.getScene().getEntityById(invoke.getEntityId()); + GameEntity entity = this.player.getScene().getEntityById(invoke.getEntityId()); if (entity == null) { return; @@ -65,7 +106,7 @@ public class AbilityManager extends BasePlayerManager { } private void handleReinitOverrideMap(AbilityInvokeEntry invoke) throws Exception { - GameEntity entity = player.getScene().getEntityById(invoke.getEntityId()); + GameEntity entity = this.player.getScene().getEntityById(invoke.getEntityId()); if (entity == null) { return; @@ -80,7 +121,7 @@ public class AbilityManager extends BasePlayerManager { private void handleModifierChange(AbilityInvokeEntry invoke) throws Exception { // Sanity checks - GameEntity target = player.getScene().getEntityById(invoke.getEntityId()); + GameEntity target = this.player.getScene().getEntityById(invoke.getEntityId()); if (target == null) { return; } @@ -104,7 +145,7 @@ public class AbilityManager extends BasePlayerManager { return; } - GameEntity sourceEntity = player.getScene().getEntityById(data.getApplyEntityId()); + GameEntity sourceEntity = this.player.getScene().getEntityById(data.getApplyEntityId()); if (sourceEntity == null) { return; } @@ -117,7 +158,7 @@ public class AbilityManager extends BasePlayerManager { if (modifier != null && modifier.getOnAdded().size() > 0) { for (AbilityModifierAction action : modifier.getOnAdded()) { - invokeAction(action, target, sourceEntity); + this.invokeAction(action, target, sourceEntity); } } @@ -133,7 +174,7 @@ public class AbilityManager extends BasePlayerManager { if (modifier != null && modifier.getOnRemoved().size() > 0) { for (AbilityModifierAction action : modifier.getOnRemoved()) { - invokeAction(action, target, sourceEntity); + this.invokeAction(action, target, sourceEntity); } } @@ -145,7 +186,7 @@ public class AbilityManager extends BasePlayerManager { private void handleMixinCostStamina(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException { AbilityMixinCostStamina costStamina = AbilityMixinCostStamina.parseFrom((invoke.getAbilityData())); - getPlayer().getStaminaManager().handleMixinCostStamina(costStamina.getIsSwim()); + this.getPlayer().getStaminaManager().handleMixinCostStamina(costStamina.getIsSwim()); } private void handleGenerateElemBall(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException { diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerClientAbilityInitFinishNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerClientAbilityInitFinishNotify.java index de87df3f2..1a0e3c138 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerClientAbilityInitFinishNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerClientAbilityInitFinishNotify.java @@ -11,20 +11,23 @@ import emu.grasscutter.utils.Utils; @Opcodes(PacketOpcodes.ClientAbilityInitFinishNotify) public class HandlerClientAbilityInitFinishNotify extends PacketHandler { - + @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { ClientAbilityInitFinishNotify notif = ClientAbilityInitFinishNotify.parseFrom(payload); Player player = session.getPlayer(); + + // Call skill end in the player's ability manager. + player.getAbilityManager().onSkillEnd(player); + for (AbilityInvokeEntry entry : notif.getInvokesList()) { player.getAbilityManager().onAbilityInvoke(entry); player.getClientAbilityInitFinishHandler().addEntry(entry.getForwardType(), entry); } - + if (notif.getInvokesList().size() > 0) { session.getPlayer().getClientAbilityInitFinishHandler().update(session.getPlayer()); } } - -} +} \ No newline at end of file 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 825a3cc02..36296ed3c 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerCombatInvocationsNotify.java @@ -6,7 +6,6 @@ import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes; -import emu.grasscutter.net.proto.AttackResultOuterClass; import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult; import emu.grasscutter.net.proto.CombatInvocationsNotifyOuterClass.CombatInvocationsNotify; import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry; @@ -18,7 +17,6 @@ import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; import emu.grasscutter.net.proto.PlayerDieTypeOuterClass; import emu.grasscutter.server.event.entity.EntityMoveEvent; -import emu.grasscutter.server.event.game.ReceiveCommandFeedbackEvent; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.utils.Position; @@ -35,23 +33,29 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { CombatInvocationsNotify notif = CombatInvocationsNotify.parseFrom(payload); for (CombatInvokeEntry entry : notif.getInvokeListList()) { // Handle combat invoke - switch (entry.getArgumentType()) { - case COMBAT_TYPE_ARGUMENT_EVT_BEING_HIT: - EvtBeingHitInfo hitInfo = EvtBeingHitInfo.parseFrom(entry.getCombatData()); - AttackResult attackResult = hitInfo.getAttackResult(); - Player player = session.getPlayer(); + switch (entry.getArgumentType()) { + case COMBAT_TYPE_ARGUMENT_EVT_BEING_HIT -> { + EvtBeingHitInfo hitInfo = EvtBeingHitInfo.parseFrom(entry.getCombatData()); + AttackResult attackResult = hitInfo.getAttackResult(); + Player player = session.getPlayer(); - // Handle damage - player.getAttackResults().add(attackResult); - player.getEnergyManager().handleAttackHit(hitInfo); - break; - case COMBAT_TYPE_ARGUMENT_ENTITY_MOVE: - // Handle movement - EntityMoveInfo moveInfo = EntityMoveInfo.parseFrom(entry.getCombatData()); - GameEntity entity = session.getPlayer().getScene().getEntityById(moveInfo.getEntityId()); - if (entity != null) { - // Move player - MotionInfo motionInfo = moveInfo.getMotionInfo(); + // Check if the player is invulnerable. + if ( + attackResult.getAttackerId() != player.getTeamManager().getCurrentAvatarEntity().getId() && + player.getAbilityManager().isAbilityInvulnerable() + ) break; + + // Handle damage + player.getAttackResults().add(attackResult); + player.getEnergyManager().handleAttackHit(hitInfo); + } + case COMBAT_TYPE_ARGUMENT_ENTITY_MOVE -> { + // Handle movement + EntityMoveInfo moveInfo = EntityMoveInfo.parseFrom(entry.getCombatData()); + GameEntity entity = session.getPlayer().getScene().getEntityById(moveInfo.getEntityId()); + if (entity != null) { + // Move player + MotionInfo motionInfo = moveInfo.getMotionInfo(); MotionState motionState = motionInfo.getState(); // Call entity move event. @@ -60,48 +64,47 @@ public class HandlerCombatInvocationsNotify extends PacketHandler { new Position(motionInfo.getRot()), motionState); event.call(); - entity.move(event.getPosition(), event.getRotation()); - entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime()); - entity.setLastMoveReliableSeq(moveInfo.getReliableSeq()); - entity.setMotionState(motionState); + entity.move(event.getPosition(), event.getRotation()); + entity.setLastMoveSceneTimeMs(moveInfo.getSceneTime()); + entity.setLastMoveReliableSeq(moveInfo.getReliableSeq()); + entity.setMotionState(motionState); - session.getPlayer().getStaminaManager().handleCombatInvocationsNotify(session, moveInfo, entity); + session.getPlayer().getStaminaManager().handleCombatInvocationsNotify(session, moveInfo, entity); - // TODO: handle MOTION_FIGHT landing which has a different damage factor - // Also, for plunge attacks, LAND_SPEED is always -30 and is not useful. - // May need the height when starting plunge attack. + // TODO: handle MOTION_FIGHT landing which has a different damage factor + // Also, for plunge attacks, LAND_SPEED is always -30 and is not useful. + // May need the height when starting plunge attack. - // MOTION_LAND_SPEED and MOTION_FALL_ON_GROUND arrive in different packets. - // Cache land speed for later use. - if (motionState == MotionState.MOTION_STATE_LAND_SPEED) { - cachedLandingSpeed = motionInfo.getSpeed().getY(); - cachedLandingTimeMillisecond = System.currentTimeMillis(); - monitorLandingEvent = true; - } - if (monitorLandingEvent) { - if (motionState == MotionState.MOTION_STATE_FALL_ON_GROUND) { - monitorLandingEvent = false; - handleFallOnGround(session, entity, motionState); - } - } - - // MOTION_STATE_NOTIFY = Dont send to other players - if (motionState == MotionState.MOTION_STATE_NOTIFY) { - continue; - } - } - break; - case COMBAT_TYPE_ARGUMENT_ANIMATOR_PARAMETER_CHANGED: - EvtAnimatorParameterInfo paramInfo = EvtAnimatorParameterInfo.parseFrom(entry.getCombatData()); - - if (paramInfo.getIsServerCache()) { - paramInfo = paramInfo.toBuilder().setIsServerCache(false).build(); - entry = entry.toBuilder().setCombatData(paramInfo.toByteString()).build(); - } - break; - default: - break; - } + // MOTION_LAND_SPEED and MOTION_FALL_ON_GROUND arrive in different packets. + // Cache land speed for later use. + if (motionState == MotionState.MOTION_STATE_LAND_SPEED) { + cachedLandingSpeed = motionInfo.getSpeed().getY(); + cachedLandingTimeMillisecond = System.currentTimeMillis(); + monitorLandingEvent = true; + } + if (monitorLandingEvent) { + if (motionState == MotionState.MOTION_STATE_FALL_ON_GROUND) { + monitorLandingEvent = false; + handleFallOnGround(session, entity, motionState); + } + } + + // MOTION_STATE_NOTIFY = Dont send to other players + if (motionState == MotionState.MOTION_STATE_NOTIFY) { + continue; + } + } + } + case COMBAT_TYPE_ARGUMENT_ANIMATOR_PARAMETER_CHANGED -> { + EvtAnimatorParameterInfo paramInfo = EvtAnimatorParameterInfo.parseFrom(entry.getCombatData()); + if (paramInfo.getIsServerCache()) { + paramInfo = paramInfo.toBuilder().setIsServerCache(false).build(); + entry = entry.toBuilder().setCombatData(paramInfo.toByteString()).build(); + } + } + default -> { + } + } session.getPlayer().getCombatInvokeHandler().addEntry(entry.getForwardType(), entry); } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java index a714d936b..dc282d27e 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java @@ -12,10 +12,16 @@ public class HandlerEvtDoSkillSuccNotify extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { EvtDoSkillSuccNotify notify = EvtDoSkillSuccNotify.parseFrom(payload); + + var player = session.getPlayer(); int skillId = notify.getSkillId(); int casterId = notify.getCasterId(); - session.getPlayer().getStaminaManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); - session.getPlayer().getEnergyManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); + // Call skill perform in the player's ability manager. + player.getAbilityManager().onSkillStart(session.getPlayer(), skillId, casterId); + + // Handle skill notify in other managers. + player.getStaminaManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); + player.getEnergyManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); } -} +} \ No newline at end of file