From d224178a645667801b7a81c8139957c4ba2e4855 Mon Sep 17 00:00:00 2001 From: longfruit <147137915+longfruit@users.noreply.github.com> Date: Sun, 5 Nov 2023 11:57:17 -0800 Subject: [PATCH] Only deduct energy when elemental burst actually fires (#2424) --- .../data/binout/AbilityModifier.java | 1 + .../emu/grasscutter/game/ability/Ability.java | 26 ++++++++ .../game/ability/AbilityManager.java | 60 ++++++++++++++++++- .../game/managers/energy/EnergyManager.java | 8 ++- .../recv/HandlerEvtDoSkillSuccNotify.java | 1 - 5 files changed, 91 insertions(+), 5 deletions(-) diff --git a/src/main/java/emu/grasscutter/data/binout/AbilityModifier.java b/src/main/java/emu/grasscutter/data/binout/AbilityModifier.java index b0abfb5e5..896751926 100644 --- a/src/main/java/emu/grasscutter/data/binout/AbilityModifier.java +++ b/src/main/java/emu/grasscutter/data/binout/AbilityModifier.java @@ -327,6 +327,7 @@ public class AbilityModifier implements Serializable { public String srcKey, dstKey; public int skillID; + public int resistanceListID; public AbilityModifierAction[] actions; public AbilityModifierAction[] successActions; diff --git a/src/main/java/emu/grasscutter/game/ability/Ability.java b/src/main/java/emu/grasscutter/game/ability/Ability.java index 4bd07c8fd..71362afd9 100644 --- a/src/main/java/emu/grasscutter/game/ability/Ability.java +++ b/src/main/java/emu/grasscutter/game/ability/Ability.java @@ -2,6 +2,7 @@ package emu.grasscutter.game.ability; import emu.grasscutter.data.GameData; import emu.grasscutter.data.binout.AbilityData; +import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.player.Player; import emu.grasscutter.net.proto.AbilityStringOuterClass.AbilityString; @@ -24,6 +25,7 @@ public class Ability { private static Map> abilitySpecialsModified = new HashMap<>(); @Getter private int hash; + @Getter private Set avatarSkillStartIds; public Ability(AbilityData data, GameEntity owner, Player playerOwner) { this.data = data; @@ -44,6 +46,30 @@ public class Ability { hash = Utils.abilityHash(data.abilityName); data.initialize(); + + // + // Collect skill IDs referenced by AvatarSkillStart modifier actions + // in onAbilityStart and in every modifier's onAdded action set. + // These skill IDs will be used by AbilityManager to determine whether + // an elemental burst has fired correctly. + // + avatarSkillStartIds = new HashSet<>(); + if (data.onAbilityStart != null) { + avatarSkillStartIds.addAll(Arrays.stream(data.onAbilityStart) + .filter(action -> + action.type == AbilityModifierAction.Type.AvatarSkillStart) + .map(action -> action.skillID) + .toList()); + } + avatarSkillStartIds.addAll(data.modifiers.values() + .stream() + .map(m -> (List)(m.onAdded == null ? + Collections.emptyList() : + Arrays.asList(m.onAdded))) + .flatMap(List::stream) + .filter(action -> action.type == AbilityModifierAction.Type.AvatarSkillStart) + .map(action -> action.skillID) + .toList()); } public static String getAbilityName(AbilityString abString) { diff --git a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java index deb14fe54..ddc16a45a 100644 --- a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java +++ b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java @@ -21,8 +21,9 @@ import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalar import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction; import emu.grasscutter.server.event.player.PlayerUseSkillEvent; import io.netty.util.concurrent.FastThreadLocalThread; -import java.util.HashMap; +import java.util.*; import java.util.concurrent.*; +import java.util.function.Consumer; import lombok.Getter; public final class AbilityManager extends BasePlayerManager { @@ -48,9 +49,56 @@ public final class AbilityManager extends BasePlayerManager { } @Getter private boolean abilityInvulnerable = false; + @Getter private Consumer clearBurstEnergy; + private int burstCasterId; + private int burstSkillId; public AbilityManager(Player player) { super(player); + removePendingEnergyClear(); + } + + public void removePendingEnergyClear() { + this.clearBurstEnergy = null; + this.burstCasterId = 0; + this.burstSkillId = 0; + } + + private void onPossibleElementalBurst(Ability ability, AbilityModifier modifier, int entityId) { + // + // Possibly clear avatar energy spent on elemental burst + // and set invulnerability. + // + // Problem: Burst can misfire occasionally, like hitting Q when + // dashing, doing E, or switching avatars. The client would + // still send EvtDoSkillSuccNotify, but the burst may not + // actually happen. We don't know when to clear avatar energy. + // + // When burst does happen, a number of AbilityInvokeEntry will + // come in. Use the Ability it references and search for any + // modifier with type=AvatarSkillStart, skillID=burst skill ID. + // + // If that is missing, search for modifier action that sets + // invulnerability as a fallback. + // + if (this.burstCasterId == 0) return; + + boolean skillInvincibility = modifier.state == AbilityModifier.State.Invincible; + if (modifier.onAdded != null) { + skillInvincibility |= Arrays.stream(modifier.onAdded) + .filter(action -> + action.type == AbilityModifierAction.Type.AttachAbilityStateResistance && + action.resistanceListID == 11002) + .toList().size() > 0; + } + + if (this.clearBurstEnergy != null && this.burstCasterId == entityId && + (ability.getAvatarSkillStartIds().contains(this.burstSkillId) || skillInvincibility)) { + Grasscutter.getLogger().trace("Caster ID's {} burst successful, clearing energy and setting invulnerability", entityId); + this.abilityInvulnerable = true; + this.clearBurstEnergy.accept(entityId); + this.removePendingEnergyClear(); + } } public static void registerHandlers() { @@ -280,8 +328,12 @@ public final class AbilityManager extends BasePlayerManager { return; } - // Set the player as invulnerable. - this.abilityInvulnerable = true; + // Track this elemental burst to possibly clear avatar energy later. + this.clearBurstEnergy = (ignored) -> + player.getEnergyManager().handleEvtDoSkillSuccNotify( + player.getSession(), skillId, casterId); + this.burstCasterId = casterId; + this.burstSkillId = skillId; } /** @@ -454,6 +506,8 @@ public final class AbilityManager extends BasePlayerManager { modifierData); } + onPossibleElementalBurst(instancedAbility, modifierData, invoke.getEntityId()); + AbilityModifierController modifier = new AbilityModifierController(instancedAbility, instancedAbilityData, modifierData); diff --git a/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java b/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java index ead4f49f0..18bc3ef91 100644 --- a/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java +++ b/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java @@ -259,8 +259,14 @@ public class EnergyManager extends BasePlayerManager { return; } + // Also reference AvatarSkillData in case the burst gets a different skill ID + // when the avatar is in a different state. For example, Wanderer's burst is + // 10755 usually but when he floats, it becomes 10753. + var skillData = GameData.getAvatarSkillDataMap().get(skillId); + // If the cast skill was a burst, consume energy. - if (avatar.getSkillDepot() != null && skillId == avatar.getSkillDepot().getEnergySkill()) { + if ((avatar.getSkillDepot() != null && skillId == avatar.getSkillDepot().getEnergySkill()) || + (skillData != null && skillData.getCostElemVal() > 0)) { avatar.getAsEntity().clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START); } } 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 f2ba9444b..ccc033a1d 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java @@ -21,7 +21,6 @@ public class HandlerEvtDoSkillSuccNotify extends PacketHandler { // Handle skill notify in other managers. player.getStaminaManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); - player.getEnergyManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); player.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_SKILL, skillId); } }