Skip to content

Commit

Permalink
Only deduct energy when elemental burst actually fires
Browse files Browse the repository at this point in the history
  • Loading branch information
longfruit committed Nov 4, 2023
1 parent d461ee2 commit b3b7ce3
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ public enum Type {
public String srcKey, dstKey;

public int skillID;
public int resistanceListID;

public AbilityModifierAction[] actions;
public AbilityModifierAction[] successActions;
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/emu/grasscutter/game/ability/Ability.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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;
Expand All @@ -24,6 +25,7 @@ public class Ability {
private static Map<String, Object2FloatMap<String>> abilitySpecialsModified = new HashMap<>();

@Getter private int hash;
@Getter private Set<Integer> avatarSkillStartIds;

public Ability(AbilityData data, GameEntity owner, Player playerOwner) {
this.data = data;
Expand All @@ -44,6 +46,30 @@ public Ability(AbilityData data, GameEntity owner, Player playerOwner) {
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<AbilityModifierAction>)(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) {
Expand Down
60 changes: 57 additions & 3 deletions src/main/java/emu/grasscutter/game/ability/AbilityManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
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 {
Expand All @@ -48,9 +49,56 @@ public final class AbilityManager extends BasePlayerManager {
}

@Getter private boolean abilityInvulnerable = false;
@Getter private Consumer<Integer> 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() {
Expand Down Expand Up @@ -280,8 +328,12 @@ public void onSkillStart(Player player, int skillId, int casterId) {
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;
}

/**
Expand Down Expand Up @@ -454,6 +506,8 @@ private void handleModifierChange(AbilityInvokeEntry invoke) throws Exception {
modifierData);
}

onPossibleElementalBurst(instancedAbility, modifierData, invoke.getEntityId());

AbilityModifierController modifier =
new AbilityModifierController(instancedAbility, instancedAbilityData, modifierData);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,8 +259,14 @@ private void handleBurstCast(Avatar avatar, int skillId) {
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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ public void handle(GameSession session, byte[] header, byte[] payload) throws Ex

// 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);
}
}

0 comments on commit b3b7ce3

Please sign in to comment.