From 2a3708ee461a3bc1432d08857a77b2a451c7e172 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Tue, 10 May 2022 04:15:47 -0700 Subject: [PATCH] Talent moving stamina cost --- .../game/ability/AbilityManager.java | 17 +- .../managers/StaminaManager/Consumption.java | 17 +- .../StaminaManager/ConsumptionType.java | 26 +- .../StaminaManager/StaminaManager.java | 384 +++++++++++------- .../game/props/PlayerProperty.java | 2 +- .../recv/HandlerEvtDoSkillSuccNotify.java | 11 +- 6 files changed, 286 insertions(+), 171 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java index d1ae388ea..b897f3df5 100644 --- a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java +++ b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java @@ -2,27 +2,22 @@ package emu.grasscutter.game.ability; import com.google.protobuf.InvalidProtocolBufferException; -import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; -import emu.grasscutter.data.custom.AbilityModifier; import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction; -import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierActionType; import emu.grasscutter.data.def.ItemData; import emu.grasscutter.data.custom.AbilityModifierEntry; import emu.grasscutter.game.entity.EntityItem; import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.player.Player; import emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.AbilityActionGenerateElemBall; -import emu.grasscutter.net.proto.AbilityInvokeArgumentOuterClass.AbilityInvokeArgument; import emu.grasscutter.net.proto.AbilityInvokeEntryHeadOuterClass.AbilityInvokeEntryHead; import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; import emu.grasscutter.net.proto.AbilityMetaModifierChangeOuterClass.AbilityMetaModifierChange; import emu.grasscutter.net.proto.AbilityMetaReInitOverrideMapOuterClass.AbilityMetaReInitOverrideMap; -import emu.grasscutter.net.proto.AbilityScalarTypeOuterClass.AbilityScalarType; +import emu.grasscutter.net.proto.AbilityMixinCostStaminaOuterClass.AbilityMixinCostStamina; import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalarValueEntry; import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction; import emu.grasscutter.utils.Position; -import emu.grasscutter.utils.Utils; public class AbilityManager { private Player player; @@ -138,13 +133,9 @@ public class AbilityManager { } } - private void handleMixinCostStamina(AbilityInvokeEntry invoke) { - // Not the right way of doing this - if (Grasscutter.getConfig().OpenStamina) { - // getPlayer().getStaminaManager().updateStamina(getPlayer().getSession(), -450); - // TODO - // set flag in stamina/movement manager that specifies the player is currently using an alternate sprint - } + private void handleMixinCostStamina(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException { + AbilityMixinCostStamina costStamina = AbilityMixinCostStamina.parseFrom((invoke.getAbilityData())); + getPlayer().getStaminaManager().handleMixinCostStamina(costStamina.getIsSwim()); } private void handleGenerateElemBall(AbilityInvokeEntry invoke) throws InvalidProtocolBufferException { diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java index 23eb44be9..a6185f063 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/Consumption.java @@ -1,15 +1,18 @@ package emu.grasscutter.game.managers.StaminaManager; public class Consumption { - public ConsumptionType consumptionType; - public int amount; + public ConsumptionType type = ConsumptionType.None; + public int amount = 0; - public Consumption(ConsumptionType ct, int a) { - consumptionType = ct; - amount = a; + public Consumption(ConsumptionType type, int amount) { + this.type = type; + this.amount = amount; } - public Consumption(ConsumptionType ct) { - this(ct, ct.amount); + public Consumption(ConsumptionType type) { + this(type, type.amount); + } + + public Consumption() { } } diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java index 9afb2171c..feb42d14e 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java @@ -4,23 +4,29 @@ public enum ConsumptionType { None(0), // consume - CLIMB_START(-500), CLIMBING(-150), + CLIMB_START(-500), CLIMB_JUMP(-2500), - SPRINT(-1800), DASH(-360), - FLY(-60), - SWIM_DASH_START(-20), - SWIM_DASH(-204), - SWIMMING(-80), // TODO: Slow swimming is handled per movement, not per second. Movement frequency depends on gender/age/height. FIGHT(0), // See StaminaManager.getFightConsumption() + FLY(-60), + // Slow swimming is handled per movement, not per second. + // Arm movement frequency depends on gender/age/height. + // TODO: Instead of cost -80 per tick, find a proper way to calculate cost. + SKIFF(-300), // TODO: Get real value + SPRINT(-1800), + SWIM_DASH_START(-20), + SWIM_DASH(-204), // -10.2 per second, 5Hz = -204 each tick + SWIMMING(-80), + TALENT_DASH(-300), // -1500 per second, 5Hz = -300 each tick + TALENT_DASH_START(-1000), // restore - STANDBY(500), + POWERED_FLY(500), // TODO: Get real value + POWERED_SKIFF(2000), // TODO: Get real value RUN(500), - WALK(500), - STANDBY_MOVE(500), - POWERED_FLY(500); + STANDBY(500), + WALK(500); public final int amount; diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index 937ba3b3f..cde40ee57 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -8,7 +8,6 @@ import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.LifeState; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.net.proto.EntityMoveInfoOuterClass.EntityMoveInfo; -import emu.grasscutter.net.proto.EvtDoSkillSuccNotifyOuterClass.EvtDoSkillSuccNotify; import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; @@ -16,13 +15,94 @@ import emu.grasscutter.net.proto.VectorOuterClass.Vector; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.Position; +import org.jetbrains.annotations.NotNull; import java.lang.Math; import java.util.*; public class StaminaManager { + + // TODO: Skiff state detection? private final Player player; - private HashMap> MotionStatesCategorized = new HashMap<>(); + private final HashMap> MotionStatesCategorized = new HashMap<>() {{ + put("CLIMB", new HashSet<>(List.of( + MotionState.MOTION_CLIMB, // sustained, when not moving no cost no recover + MotionState.MOTION_STANDBY_TO_CLIMB // NOT OBSERVED, see MOTION_JUMP_UP_WALL_FOR_STANDBY + ))); + put("DASH", new HashSet<>(List.of( + MotionState.MOTION_DANGER_DASH, // sustained + MotionState.MOTION_DASH // sustained + ))); + put("FLY", new HashSet<>(List.of( + MotionState.MOTION_FLY, // sustained + MotionState.MOTION_FLY_FAST, // sustained + MotionState.MOTION_FLY_SLOW, // sustained + MotionState.MOTION_POWERED_FLY // sustained, recover + ))); + put("RUN", new HashSet<>(List.of( + MotionState.MOTION_DANGER_RUN, // sustained, recover + MotionState.MOTION_RUN // sustained, recover + ))); + put("SKIFF", new HashSet<>(List.of( + MotionState.MOTION_SKIFF_BOARDING, // NOT OBSERVED even when boarding + MotionState.MOTION_SKIFF_DASH, // NOT OBSERVED even when dashing + MotionState.MOTION_SKIFF_NORMAL, // sustained, OBSERVED when both normal and dashing + MotionState.MOTION_SKIFF_POWERED_DASH // sustained, recover + ))); + put("STANDBY", new HashSet<>(List.of( + MotionState.MOTION_DANGER_STANDBY_MOVE, // sustained, recover + MotionState.MOTION_DANGER_STANDBY, // sustained, recover + MotionState.MOTION_LADDER_TO_STANDBY, // NOT OBSERVED + MotionState.MOTION_STANDBY_MOVE, // sustained, recover + MotionState.MOTION_STANDBY // sustained, recover + ))); + put("SWIM", new HashSet<>(List.of( + MotionState.MOTION_SWIM_IDLE, // sustained + MotionState.MOTION_SWIM_DASH, // immediate and sustained + MotionState.MOTION_SWIM_JUMP, // NOT OBSERVED + MotionState.MOTION_SWIM_MOVE // sustained + ))); + put("WALK", new HashSet<>(List.of( + MotionState.MOTION_DANGER_WALK, // sustained, recover + MotionState.MOTION_WALK // sustained, recover + ))); + put("OTHER", new HashSet<>(List.of( + MotionState.MOTION_CLIMB_JUMP, // cost only once if repeated without switching state + MotionState.MOTION_DASH_BEFORE_SHAKE, // immediate one time sprint charge. + MotionState.MOTION_FIGHT, // immediate, if sustained then subsequent will be MOTION_NOTIFY + MotionState.MOTION_JUMP_UP_WALL_FOR_STANDBY, // immediate, observed when RUN/WALK->CLIMB + MotionState.MOTION_NOTIFY, // can be either cost or recover - check previous state and check skill casting + MotionState.MOTION_SIT_IDLE, // sustained, recover + MotionState.MOTION_JUMP // recover + ))); + put("NOCOST_NORECOVER", new HashSet<>(List.of( + MotionState.MOTION_LADDER_SLIP, // NOT OBSERVED + MotionState.MOTION_SLIP, // sustained, no cost no recover + MotionState.MOTION_FLY_IDLE // NOT OBSERVED + ))); + put("IGNORE", new HashSet<>(List.of( + // these states have no impact on stamina + MotionState.MOTION_CROUCH_IDLE, + MotionState.MOTION_CROUCH_MOVE, + MotionState.MOTION_CROUCH_ROLL, + MotionState.MOTION_DESTROY_VEHICLE, + MotionState.MOTION_FALL_ON_GROUND, + MotionState.MOTION_FOLLOW_ROUTE, + MotionState.MOTION_FORCE_SET_POS, + MotionState.MOTION_GO_UPSTAIRS, + MotionState.MOTION_JUMP_OFF_WALL, + MotionState.MOTION_LADDER_IDLE, + MotionState.MOTION_LADDER_MOVE, + MotionState.MOTION_LAND_SPEED, + MotionState.MOTION_MOVE_FAIL_ACK, + MotionState.MOTION_NONE, + MotionState.MOTION_NUM, + MotionState.MOTION_QUEST_FORCE_DRAG, + MotionState.MOTION_RESET, + MotionState.MOTION_STANDBY_TO_LADDER, + MotionState.MOTION_WATERFALL + ))); + }}; public final static int GlobalMaximumStamina = 24000; private Position currentCoordinates = new Position(0, 0, 0); @@ -33,70 +113,23 @@ public class StaminaManager { private GameSession cachedSession = null; private GameEntity cachedEntity = null; private int staminaRecoverDelay = 0; + private final HashMap beforeUpdateStaminaListeners = new HashMap<>(); + private final HashMap afterUpdateStaminaListeners = new HashMap<>(); + private int lastSkillId = 0; + private int lastSkillCasterId = 0; + private boolean lastSkillFirstTick = true; - private HashMap beforeUpdateStaminaListeners = new HashMap<>(); - private HashMap afterUpdateStaminaListeners = new HashMap<>(); public StaminaManager(Player player) { this.player = player; - - MotionStatesCategorized.put("SWIM", new HashSet<>(Arrays.asList( - MotionState.MOTION_SWIM_MOVE, - MotionState.MOTION_SWIM_IDLE, - MotionState.MOTION_SWIM_DASH, - MotionState.MOTION_SWIM_JUMP - ))); - - MotionStatesCategorized.put("STANDBY", new HashSet<>(Arrays.asList( - MotionState.MOTION_STANDBY, - MotionState.MOTION_STANDBY_MOVE, - MotionState.MOTION_DANGER_STANDBY, - MotionState.MOTION_DANGER_STANDBY_MOVE, - MotionState.MOTION_LADDER_TO_STANDBY, - MotionState.MOTION_JUMP_UP_WALL_FOR_STANDBY - ))); - - MotionStatesCategorized.put("CLIMB", new HashSet<>(Arrays.asList( - MotionState.MOTION_CLIMB, - MotionState.MOTION_CLIMB_JUMP, - MotionState.MOTION_STANDBY_TO_CLIMB, - MotionState.MOTION_LADDER_IDLE, - MotionState.MOTION_LADDER_MOVE, - MotionState.MOTION_LADDER_SLIP, - MotionState.MOTION_STANDBY_TO_LADDER - ))); - - MotionStatesCategorized.put("FLY", new HashSet<>(Arrays.asList( - MotionState.MOTION_FLY, - MotionState.MOTION_FLY_IDLE, - MotionState.MOTION_FLY_SLOW, - MotionState.MOTION_FLY_FAST, - MotionState.MOTION_POWERED_FLY - ))); - - MotionStatesCategorized.put("RUN", new HashSet<>(Arrays.asList( - MotionState.MOTION_DASH, - MotionState.MOTION_DANGER_DASH, - MotionState.MOTION_DASH_BEFORE_SHAKE, - MotionState.MOTION_RUN, - MotionState.MOTION_DANGER_RUN, - MotionState.MOTION_WALK, - MotionState.MOTION_DANGER_WALK - ))); - - MotionStatesCategorized.put("FIGHT", new HashSet<>(Arrays.asList( - MotionState.MOTION_FIGHT - ))); - - MotionStatesCategorized.put("SKIFF", new HashSet<>(Arrays.asList( - MotionState.MOTION_SKIFF_BOARDING, - MotionState.MOTION_SKIFF_NORMAL, - MotionState.MOTION_SKIFF_DASH, - MotionState.MOTION_SKIFF_POWERED_DASH - ))); } - // Listeners + // Accessors + + public void setSkillCast(int skillId, int skillCasterId) { + lastSkillId = skillId; + lastSkillCasterId = skillCasterId; + } public boolean registerBeforeUpdateStaminaListener(String listenerName, BeforeUpdateStaminaListener listener) { if (beforeUpdateStaminaListeners.containsKey(listenerName)) { @@ -146,17 +179,17 @@ public class StaminaManager { } // notify will update for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { - Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.consumptionType.toString(), consumption); - if ((overriddenConsumption.consumptionType != consumption.consumptionType) && (overriddenConsumption.amount != consumption.amount)) { + Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.type.toString(), consumption); + if ((overriddenConsumption.type != consumption.type) && (overriddenConsumption.amount != consumption.amount)) { Grasscutter.getLogger().debug("[StaminaManager] Stamina update relative(" + - consumption.consumptionType.toString() + ", " + consumption.amount + ") overridden to relative(" + - consumption.consumptionType.toString() + ", " + consumption.amount + ") by: " + listener.getKey()); + consumption.type.toString() + ", " + consumption.amount + ") overridden to relative(" + + consumption.type.toString() + ", " + consumption.amount + ") by: " + listener.getKey()); return currentStamina; } } int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); Grasscutter.getLogger().trace(currentStamina + "/" + playerMaxStamina + "\t" + currentState + "\t" + - (isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.consumptionType + "," + + (isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.type + "," + consumption.amount + ")"); int newStamina = currentStamina + consumption.amount; if (newStamina < 0) { @@ -164,7 +197,7 @@ public class StaminaManager { } else if (newStamina > playerMaxStamina) { newStamina = playerMaxStamina; } - return setStamina(session, consumption.consumptionType.toString(), newStamina); + return setStamina(session, consumption.type.toString(), newStamina); } public int updateStaminaAbsolute(GameSession session, String reason, int newStamina) { @@ -236,11 +269,24 @@ public class StaminaManager { // External trigger handler - public void handleEvtDoSkillSuccNotify(GameSession session, EvtDoSkillSuccNotify notify) { - handleImmediateStamina(session, notify); + public void handleEvtDoSkillSuccNotify(GameSession session, int skillId, int casterId) { + // Ignore if skill not cast by not current active + if (casterId != player.getTeamManager().getCurrentAvatarEntity().getId()) { + return; + } + setSkillCast(skillId, casterId); + handleImmediateStamina(session, skillId); } - public void handleCombatInvocationsNotify(GameSession session, EntityMoveInfo moveInfo, GameEntity entity) { + public void handleMixinCostStamina(boolean isSwim) { + // Talent moving and claymore avatar charged attack duration + // Grasscutter.getLogger().trace("abilityMixinCostStamina: isSwim: " + isSwim); + if (lastSkillCasterId == player.getTeamManager().getCurrentAvatarEntity().getId()) { + handleImmediateStamina(cachedSession, lastSkillId); + } + } + + public void handleCombatInvocationsNotify(@NotNull GameSession session, @NotNull EntityMoveInfo moveInfo, @NotNull GameEntity entity) { // cache info for later use in SustainedStaminaHandler tick cachedSession = session; cachedEntity = entity; @@ -252,20 +298,25 @@ public class StaminaManager { return; } currentState = motionState; + // Grasscutter.getLogger().trace("" + currentState); Vector posVector = motionInfo.getPos(); Position newPos = new Position(posVector.getX(), posVector.getY(), posVector.getZ()); if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) { currentCoordinates = newPos; } startSustainedStaminaHandler(); - handleImmediateStamina(session, motionInfo, motionState, entity); + handleImmediateStamina(session, motionState); } // Internal handler - private void handleImmediateStamina(GameSession session, MotionInfo motionInfo, MotionState motionState, - GameEntity entity) { + private void handleImmediateStamina(GameSession session, @NotNull MotionState motionState) { switch (motionState) { + case MOTION_CLIMB: + if (currentState != MotionState.MOTION_CLIMB) { + updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START)); + } + break; case MOTION_DASH_BEFORE_SHAKE: if (previousState != MotionState.MOTION_DASH_BEFORE_SHAKE) { updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT)); @@ -284,8 +335,10 @@ public class StaminaManager { } } - private void handleImmediateStamina(GameSession session, EvtDoSkillSuccNotify notify) { - Consumption consumption = getFightConsumption(notify.getSkillId()); + private void handleImmediateStamina(GameSession session, int skillId) { + // Non-claymore avatar attacks + // TODO: differentiate charged vs normal attack + Consumption consumption = getFightConsumption(skillId); updateStaminaRelative(session, consumption); } @@ -298,32 +351,45 @@ public class StaminaManager { Grasscutter.getLogger().trace("Player moving: " + moving + ", stamina full: " + (currentStamina >= maxStamina) + ", recalculate stamina"); - Consumption consumption = new Consumption(ConsumptionType.None); + Consumption consumption; if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { - consumption = getClimbSustainedConsumption(); - } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { - consumption = getSwimSustainedConsumptions(); - } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { - consumption = getRunWalkDashSustainedConsumption(); + consumption = getClimbConsumption(); + } else if (MotionStatesCategorized.get("DASH").contains(currentState)) { + consumption = getDashConsumption(); } else if (MotionStatesCategorized.get("FLY").contains(currentState)) { - consumption = getFlySustainedConsumption(); + consumption = getFlyConsumption(); + } else if (MotionStatesCategorized.get("RUN").contains(currentState)) { + consumption = new Consumption(ConsumptionType.RUN); + } else if (MotionStatesCategorized.get("SKIFF").contains(currentState)) { + consumption = getSkiffConsumption(); } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { - consumption = getStandSustainedConsumption(); + consumption = new Consumption(ConsumptionType.STANDBY); + } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { + consumption = getSwimConsumptions(); + } else if (MotionStatesCategorized.get("WALK").contains((currentState))) { + consumption = new Consumption(ConsumptionType.WALK); + } else if (MotionStatesCategorized.get("OTHER").contains((currentState))) { + consumption = getOtherConsumptions(); + } else { + // ignore + return; } - - /* + if (consumption.amount < 0) { + /* Do not apply reduction factor when recovering stamina TODO: Reductions that apply to all motion types: Elemental Resonance Wind: -15% Skills - Diona E: -10% while shield lasts - Barbara E: -12% while lasts - */ + Diona E: -10% while shield lasts - applies to SP+MP + Barbara E: -12% while lasts - applies to SP+MP + */ + } + // Delay 2 seconds before starts recovering stamina if (cachedSession != null) { if (consumption.amount < 0) { staminaRecoverDelay = 0; } - if (consumption.amount > 0 && consumption.consumptionType != ConsumptionType.POWERED_FLY) { + if (consumption.amount > 0 && consumption.type != ConsumptionType.POWERED_FLY) { // For POWERED_FLY recover immediately - things like Amber's gliding exam may require this. if (staminaRecoverDelay < 10) { // For others recover after 2 seconds (10 ticks) - as official server does. @@ -384,91 +450,137 @@ public class StaminaManager { */ // TODO: Currently only handling Ayaka and Mona's talent moving initial costs. - Consumption consumption = new Consumption(ConsumptionType.None); - HashMap fightingCost = new HashMap<>() {{ - put(10013, -1000); // Kamisato Ayaka - put(10413, -1000); // Mona - }}; - if (fightingCost.containsKey(skillCasting)) { - consumption = new Consumption(ConsumptionType.FIGHT, fightingCost.get(skillCasting)); - } - return consumption; - } + Consumption consumption = new Consumption(); - private Consumption getClimbSustainedConsumption() { - Consumption consumption = new Consumption(ConsumptionType.None); - if (currentState == MotionState.MOTION_CLIMB && isPlayerMoving()) { - consumption = new Consumption(ConsumptionType.CLIMBING); - if (previousState != MotionState.MOTION_CLIMB && previousState != MotionState.MOTION_CLIMB_JUMP) { - consumption = new Consumption(ConsumptionType.CLIMB_START); + // Talent moving + HashMap> talentMovementConsumptions = new HashMap<>() {{ + // List[0] = initial cost, [1] = sustained cost. Sustained costs are divided by 3 per second as MixinStaminaCost is triggered at 3Hz. + put(10013, List.of(new Consumption(ConsumptionType.TALENT_DASH_START, -1000), new Consumption(ConsumptionType.TALENT_DASH, -500))); // Kamisato Ayaka + put(10413, List.of(new Consumption(ConsumptionType.TALENT_DASH_START, -1000), new Consumption(ConsumptionType.TALENT_DASH, -500))); // Mona + }}; + if (talentMovementConsumptions.containsKey(skillCasting)) { + if (lastSkillFirstTick) { + consumption = talentMovementConsumptions.get(skillCasting).get(1); + } else { + lastSkillFirstTick = false; + consumption = talentMovementConsumptions.get(skillCasting).get(0); } } - // TODO: Foods + // TODO: Claymore avatar charged attack + // HashMap fightConsumptions = new HashMap<>(); + + // TODO: Non-claymore avatar charged attack + return consumption; } - private Consumption getSwimSustainedConsumptions() { + private Consumption getClimbConsumption() { + Consumption consumption = new Consumption(); + if (currentState == MotionState.MOTION_CLIMB && isPlayerMoving()) { + consumption.type = ConsumptionType.CLIMBING; + consumption.amount = ConsumptionType.CLIMBING.amount; + } + // Climbing specific reductions + // TODO: create a food cost reduction map + HashMap foodReductionMap = new HashMap<>() {{ + // TODO: get real talent id + put(0, 0.8f); // Sample food + }}; + consumption.amount *= getFoodCostReductionFactor(foodReductionMap); + + HashMap talentReductionMap = new HashMap<>() {{ + // TODO: get real talent id + put(0, 0.8f); // Xiao + }}; + consumption.amount *= getTalentCostReductionFactor(talentReductionMap); + return consumption; + } + + private Consumption getSwimConsumptions() { handleDrowning(); - Consumption consumption = new Consumption(ConsumptionType.None); + Consumption consumption = new Consumption(); if (currentState == MotionState.MOTION_SWIM_MOVE) { - consumption = new Consumption(ConsumptionType.SWIMMING); + consumption.type = ConsumptionType.SWIMMING; + consumption.amount = ConsumptionType.SWIMMING.amount; } if (currentState == MotionState.MOTION_SWIM_DASH) { - consumption = new Consumption(ConsumptionType.SWIM_DASH); + consumption.type = ConsumptionType.SWIM_DASH; + consumption.amount = ConsumptionType.SWIM_DASH.amount; } + // Reductions + HashMap talentReductionMap = new HashMap<>() {{ + // TODO: get real talent id + put(0, 0.8f); // Beidou + put(1, 0.8f); // Sangonomiya Kokomi + }}; + consumption.amount *= getTalentCostReductionFactor(talentReductionMap); return consumption; } - private Consumption getRunWalkDashSustainedConsumption() { - Consumption consumption = new Consumption(ConsumptionType.None); + private Consumption getDashConsumption() { + Consumption consumption = new Consumption(); if (currentState == MotionState.MOTION_DASH) { - consumption = new Consumption(ConsumptionType.DASH); - // TODO: Foods - } - if (currentState == MotionState.MOTION_RUN) { - consumption = new Consumption(ConsumptionType.RUN); - } - if (currentState == MotionState.MOTION_WALK) { - consumption = new Consumption(ConsumptionType.WALK); + consumption.type = ConsumptionType.DASH; + consumption.amount = ConsumptionType.DASH.amount; + // TODO: Dashing specific reductions + // Foods: } return consumption; } - private Consumption getFlySustainedConsumption() { + private Consumption getFlyConsumption() { // POWERED_FLY, e.g. wind tunnel if (currentState == MotionState.MOTION_POWERED_FLY) { return new Consumption(ConsumptionType.POWERED_FLY); } Consumption consumption = new Consumption(ConsumptionType.FLY); - // Talent - HashMap glidingCostReduction = new HashMap<>() {{ + // Passive Talents + HashMap talentReductionMap = new HashMap<>() {{ put(212301, 0.8f); // Amber put(222301, 0.8f); // Venti }}; + consumption.amount *= getTalentCostReductionFactor(talentReductionMap); + // TODO: Foods + return consumption; + } + + private Consumption getSkiffConsumption() { + // POWERED_SKIFF, e.g. wind tunnel + if (currentState == MotionState.MOTION_SKIFF_POWERED_DASH) { + return new Consumption(ConsumptionType.POWERED_SKIFF); + } + Consumption consumption = new Consumption(ConsumptionType.SKIFF); + // No known reduction for skiffing. + return consumption; + } + + private Consumption getOtherConsumptions() { + // TODO: Add logic + return new Consumption(); + } + + // Reduction getter + + private float getTalentCostReductionFactor(HashMap talentReductionMap) { + // All known talents reductions are not stackable float reduction = 1; for (EntityAvatar entity : cachedSession.getPlayer().getTeamManager().getActiveTeam()) { for (int skillId : entity.getAvatar().getProudSkillList()) { - if (glidingCostReduction.containsKey(skillId)) { - float potentialLowerReduction = glidingCostReduction.get(skillId); + if (talentReductionMap.containsKey(skillId)) { + float potentialLowerReduction = talentReductionMap.get(skillId); if (potentialLowerReduction < reduction) { reduction = potentialLowerReduction; } } } } - consumption.amount *= reduction; - // TODO: Foods - return consumption; + return reduction; } - private Consumption getStandSustainedConsumption() { - Consumption consumption = new Consumption(ConsumptionType.None); - if (currentState == MotionState.MOTION_STANDBY) { - consumption = new Consumption(ConsumptionType.STANDBY); - } - if (currentState == MotionState.MOTION_STANDBY_MOVE) { - consumption = new Consumption(ConsumptionType.STANDBY_MOVE); - } - return consumption; + private float getFoodCostReductionFactor(HashMap foodReductionMap) { + // All known food reductions are not stackable + // TODO: Check consumed food (buff?) and return proper factor + float reduction = 1; + return reduction; } } diff --git a/src/main/java/emu/grasscutter/game/props/PlayerProperty.java b/src/main/java/emu/grasscutter/game/props/PlayerProperty.java index 3cb67d9bb..85a9456cf 100644 --- a/src/main/java/emu/grasscutter/game/props/PlayerProperty.java +++ b/src/main/java/emu/grasscutter/game/props/PlayerProperty.java @@ -30,7 +30,7 @@ public enum PlayerProperty { // his gems and then got a money refund, so negative is allowed. PROP_PLAYER_SCOIN (10016), // Mora [0, +inf) PROP_PLAYER_MP_SETTING_TYPE (10017), // Do you allow other players to join your game? [0=no 1=direct 2=approval] - PROP_IS_MP_MODE_AVAILABLE (10018), // Are you not in a quest or something that disables MP? [0, 1] + PROP_IS_MP_MODE_AVAILABLE (10018), // 0 if in quest or something that disables MP [0, 1] PROP_PLAYER_WORLD_LEVEL (10019), // [0, 8] PROP_PLAYER_RESIN (10020), // Original Resin [0, +inf) PROP_PLAYER_WAIT_SUB_HCOIN (10022), 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 705341fa0..36aa733c2 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java @@ -4,7 +4,9 @@ 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.net.proto.VectorOuterClass.Vector; import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.utils.Position; @Opcodes(PacketOpcodes.EvtDoSkillSuccNotify) public class HandlerEvtDoSkillSuccNotify extends PacketHandler { @@ -12,9 +14,10 @@ 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. - - session.getPlayer().getStaminaManager().handleEvtDoSkillSuccNotify(session, notify); + int skillId = notify.getSkillId(); + int casterId = notify.getCasterId(); + Vector forwardVector = notify.getForward(); + Position forward = new Position(forwardVector.getX(), forwardVector.getY(), forwardVector.getZ()); + session.getPlayer().getStaminaManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); } - }