Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only deduct energy when elemental burst actually fires #2424

Merged
merged 1 commit into from
Nov 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}