|
|
|
|
@@ -33,94 +33,170 @@ public class StaminaManager {
|
|
|
|
|
private GameSession cachedSession = null;
|
|
|
|
|
private GameEntity cachedEntity = null;
|
|
|
|
|
private int staminaRecoverDelay = 0;
|
|
|
|
|
private boolean isInSkillMove = false;
|
|
|
|
|
public boolean getIsInSkillMove() {
|
|
|
|
|
return isInSkillMove;
|
|
|
|
|
}
|
|
|
|
|
public void setIsInSkillMove(boolean b) {
|
|
|
|
|
isInSkillMove = b;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private HashMap<String, BeforeUpdateStaminaListener> beforeUpdateStaminaListeners = new HashMap<>();
|
|
|
|
|
private HashMap<String, AfterUpdateStaminaListener> 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
|
|
|
|
|
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
|
|
|
|
|
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
|
|
|
|
|
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
|
|
|
|
|
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
|
|
|
|
|
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
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
public boolean registerBeforeUpdateStaminaListener(String listenerName, BeforeUpdateStaminaListener listener) {
|
|
|
|
|
if (beforeUpdateStaminaListeners.containsKey(listenerName)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
beforeUpdateStaminaListeners.put(listenerName, listener);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public boolean unregisterBeforeUpdateStaminaListener(String listenerName) {
|
|
|
|
|
if (!beforeUpdateStaminaListeners.containsKey(listenerName)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
beforeUpdateStaminaListeners.remove(listenerName);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public boolean registerAfterUpdateStaminaListener(String listenerName, AfterUpdateStaminaListener listener) {
|
|
|
|
|
if (afterUpdateStaminaListeners.containsKey(listenerName)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
afterUpdateStaminaListeners.put(listenerName, listener);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public boolean unregisterAfterUpdateStaminaListener(String listenerName) {
|
|
|
|
|
if (!afterUpdateStaminaListeners.containsKey(listenerName)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
afterUpdateStaminaListeners.remove(listenerName);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean isPlayerMoving() {
|
|
|
|
|
float diffX = currentCoordinates.getX() - previousCoordinates.getX();
|
|
|
|
|
float diffY = currentCoordinates.getY() - previousCoordinates.getY();
|
|
|
|
|
float diffZ = currentCoordinates.getZ() - previousCoordinates.getZ();
|
|
|
|
|
Grasscutter.getLogger().debug("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates +
|
|
|
|
|
Grasscutter.getLogger().trace("isPlayerMoving: " + previousCoordinates + ", " + currentCoordinates +
|
|
|
|
|
", " + diffX + ", " + diffY + ", " + diffZ);
|
|
|
|
|
return Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.2 || Math.abs(diffZ) > 0.3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns new stamina and sends PlayerPropNotify
|
|
|
|
|
public int updateStamina(GameSession session, Consumption consumption) {
|
|
|
|
|
public int updateStaminaRelative(GameSession session, Consumption consumption) {
|
|
|
|
|
int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
|
|
|
|
if (consumption.amount == 0) {
|
|
|
|
|
return currentStamina;
|
|
|
|
|
}
|
|
|
|
|
// notify will update
|
|
|
|
|
for (Map.Entry<String, BeforeUpdateStaminaListener> listener : beforeUpdateStaminaListeners.entrySet()) {
|
|
|
|
|
Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.consumptionType.toString(), consumption);
|
|
|
|
|
if ((overriddenConsumption.consumptionType != consumption.consumptionType) && (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());
|
|
|
|
|
return currentStamina;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
|
|
|
|
Grasscutter.getLogger().debug(currentStamina + "/" + playerMaxStamina + "\t" + currentState + "\t" +
|
|
|
|
|
Grasscutter.getLogger().trace(currentStamina + "/" + playerMaxStamina + "\t" + currentState + "\t" +
|
|
|
|
|
(isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.consumptionType + "," +
|
|
|
|
|
consumption.amount + ")");
|
|
|
|
|
int newStamina = currentStamina + consumption.amount;
|
|
|
|
|
if (newStamina < 0) {
|
|
|
|
|
newStamina = 0;
|
|
|
|
|
}
|
|
|
|
|
if (newStamina > playerMaxStamina) {
|
|
|
|
|
} else if (newStamina > playerMaxStamina) {
|
|
|
|
|
newStamina = playerMaxStamina;
|
|
|
|
|
}
|
|
|
|
|
return setStamina(session, consumption.consumptionType.toString(), newStamina);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int updateStaminaAbsolute(GameSession session, String reason, int newStamina) {
|
|
|
|
|
int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
|
|
|
|
// notify will update
|
|
|
|
|
for (Map.Entry<String, BeforeUpdateStaminaListener> listener : beforeUpdateStaminaListeners.entrySet()) {
|
|
|
|
|
int overriddenNewStamina = listener.getValue().onBeforeUpdateStamina(reason, newStamina);
|
|
|
|
|
if (overriddenNewStamina != newStamina) {
|
|
|
|
|
Grasscutter.getLogger().debug("[StaminaManager] Stamina update absolute(" +
|
|
|
|
|
reason + ", " + newStamina + ") overridden to absolute(" +
|
|
|
|
|
reason + ", " + newStamina + ") by: " + listener.getKey());
|
|
|
|
|
return currentStamina;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
|
|
|
|
if (newStamina < 0) {
|
|
|
|
|
newStamina = 0;
|
|
|
|
|
} else if (newStamina > playerMaxStamina) {
|
|
|
|
|
newStamina = playerMaxStamina;
|
|
|
|
|
}
|
|
|
|
|
return setStamina(session, reason, newStamina);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns new stamina and sends PlayerPropNotify
|
|
|
|
|
public int setStamina(GameSession session, String reason, int newStamina) {
|
|
|
|
|
// set stamina
|
|
|
|
|
player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina);
|
|
|
|
|
session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA));
|
|
|
|
|
// notify updated
|
|
|
|
|
for (Map.Entry<String, AfterUpdateStaminaListener> listener : afterUpdateStaminaListeners.entrySet()) {
|
|
|
|
|
listener.getValue().onAfterUpdateStamina(reason, newStamina);
|
|
|
|
|
}
|
|
|
|
|
return newStamina;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -141,7 +217,7 @@ public class StaminaManager {
|
|
|
|
|
if (!player.isPaused() && sustainedStaminaHandlerTimer == null) {
|
|
|
|
|
sustainedStaminaHandlerTimer = new Timer();
|
|
|
|
|
sustainedStaminaHandlerTimer.scheduleAtFixedRate(new SustainedStaminaHandler(), 0, 200);
|
|
|
|
|
// Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer started");
|
|
|
|
|
Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer started");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -149,7 +225,7 @@ public class StaminaManager {
|
|
|
|
|
if (sustainedStaminaHandlerTimer != null) {
|
|
|
|
|
sustainedStaminaHandlerTimer.cancel();
|
|
|
|
|
sustainedStaminaHandlerTimer = null;
|
|
|
|
|
// Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer stopped");
|
|
|
|
|
Grasscutter.getLogger().debug("[MovementManager] SustainedStaminaHandlerTimer stopped");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -188,17 +264,17 @@ public class StaminaManager {
|
|
|
|
|
switch (motionState) {
|
|
|
|
|
case MOTION_DASH_BEFORE_SHAKE:
|
|
|
|
|
if (previousState != MotionState.MOTION_DASH_BEFORE_SHAKE) {
|
|
|
|
|
updateStamina(session, new Consumption(ConsumptionType.SPRINT));
|
|
|
|
|
updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case MOTION_CLIMB_JUMP:
|
|
|
|
|
if (previousState != MotionState.MOTION_CLIMB_JUMP) {
|
|
|
|
|
updateStamina(session, new Consumption(ConsumptionType.CLIMB_JUMP));
|
|
|
|
|
updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case MOTION_SWIM_DASH:
|
|
|
|
|
if (previousState != MotionState.MOTION_SWIM_DASH) {
|
|
|
|
|
updateStamina(session, new Consumption(ConsumptionType.SWIM_DASH_START));
|
|
|
|
|
updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
@@ -206,7 +282,7 @@ public class StaminaManager {
|
|
|
|
|
|
|
|
|
|
private void handleImmediateStamina(GameSession session, EvtDoSkillSuccNotify notify) {
|
|
|
|
|
Consumption consumption = getFightConsumption(notify.getSkillId());
|
|
|
|
|
updateStamina(session, consumption);
|
|
|
|
|
updateStaminaRelative(session, consumption);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private class SustainedStaminaHandler extends TimerTask {
|
|
|
|
|
@@ -216,22 +292,30 @@ public class StaminaManager {
|
|
|
|
|
int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
|
|
|
|
int maxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
|
|
|
|
if (moving || (currentStamina < maxStamina)) {
|
|
|
|
|
Grasscutter.getLogger().debug("Player moving: " + moving + ", stamina full: " +
|
|
|
|
|
Grasscutter.getLogger().trace("Player moving: " + moving + ", stamina full: " +
|
|
|
|
|
(currentStamina >= maxStamina) + ", recalculate stamina");
|
|
|
|
|
|
|
|
|
|
Consumption consumption = new Consumption(ConsumptionType.None);
|
|
|
|
|
if (!isInSkillMove) {
|
|
|
|
|
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();
|
|
|
|
|
} else if (MotionStatesCategorized.get("FLY").contains(currentState)) {
|
|
|
|
|
consumption = getFlySustainedConsumption();
|
|
|
|
|
} else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) {
|
|
|
|
|
consumption = getStandSustainedConsumption();
|
|
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
|
} else if (MotionStatesCategorized.get("FLY").contains(currentState)) {
|
|
|
|
|
consumption = getFlySustainedConsumption();
|
|
|
|
|
} else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) {
|
|
|
|
|
consumption = getStandSustainedConsumption();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
TODO: Reductions that apply to all motion types:
|
|
|
|
|
Elemental Resonance
|
|
|
|
|
Wind: -15%
|
|
|
|
|
Skills
|
|
|
|
|
Diona E: -10% while shield lasts
|
|
|
|
|
Barbara E: -12% while lasts
|
|
|
|
|
*/
|
|
|
|
|
if (cachedSession != null) {
|
|
|
|
|
if (consumption.amount < 0) {
|
|
|
|
|
staminaRecoverDelay = 0;
|
|
|
|
|
@@ -241,12 +325,12 @@ public class StaminaManager {
|
|
|
|
|
if (staminaRecoverDelay < 10) {
|
|
|
|
|
// For others recover after 2 seconds (10 ticks) - as official server does.
|
|
|
|
|
staminaRecoverDelay++;
|
|
|
|
|
consumption = new Consumption(ConsumptionType.None);
|
|
|
|
|
consumption.amount = 0;
|
|
|
|
|
Grasscutter.getLogger().trace("[StaminaManager] Delaying recovery: " + staminaRecoverDelay);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
updateStamina(cachedSession, consumption);
|
|
|
|
|
updateStaminaRelative(cachedSession, consumption);
|
|
|
|
|
}
|
|
|
|
|
handleDrowning();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
previousState = currentState;
|
|
|
|
|
@@ -261,10 +345,9 @@ public class StaminaManager {
|
|
|
|
|
private void handleDrowning() {
|
|
|
|
|
int stamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
|
|
|
|
if (stamina < 10) {
|
|
|
|
|
boolean isSwimming = MotionStatesCategorized.get("SWIM").contains(currentState);
|
|
|
|
|
Grasscutter.getLogger().debug(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" +
|
|
|
|
|
player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState + "\t" + isSwimming);
|
|
|
|
|
if (isSwimming && currentState != MotionState.MOTION_SWIM_IDLE) {
|
|
|
|
|
Grasscutter.getLogger().trace(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" +
|
|
|
|
|
player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState);
|
|
|
|
|
if (currentState != MotionState.MOTION_SWIM_IDLE) {
|
|
|
|
|
killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_DRAWN);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -272,7 +355,33 @@ public class StaminaManager {
|
|
|
|
|
|
|
|
|
|
// Consumption Calculators
|
|
|
|
|
|
|
|
|
|
// Stamina Consumption Reduction: https://genshin-impact.fandom.com/wiki/Stamina
|
|
|
|
|
|
|
|
|
|
private Consumption getFightConsumption(int skillCasting) {
|
|
|
|
|
/* TODO:
|
|
|
|
|
Instead of handling here, consider call StaminaManager.updateStamina****() with a Consumption object with
|
|
|
|
|
type=FIGHT and a modified amount when handling attacks for more accurate attack start/end time and
|
|
|
|
|
other info. Handling it here could be very complicated.
|
|
|
|
|
Charged attack
|
|
|
|
|
Default:
|
|
|
|
|
Polearm: (-2500)
|
|
|
|
|
Claymore: (-4000 per second, -800 each tick)
|
|
|
|
|
Catalyst: (-5000)
|
|
|
|
|
Talent:
|
|
|
|
|
Ningguang: When Ningguang is in possession of Star Jades, her Charged Attack does not consume Stamina. (Catalyst * 0)
|
|
|
|
|
Klee: When Jumpy Dumpty and Normal Attacks deal DMG, Klee has a 50% chance to obtain an Explosive Spark.
|
|
|
|
|
This Explosive Spark is consumed by the next Charged Attack, which costs no Stamina. (Catalyst * 0)
|
|
|
|
|
Constellations:
|
|
|
|
|
Hu Tao: While in a Paramita Papilio state activated by Guide to Afterlife, Hu Tao's Charge Attacks do not consume Stamina. (Polearm * 0)
|
|
|
|
|
Character Specific:
|
|
|
|
|
Keqing: (-2500)
|
|
|
|
|
Diluc: (Claymore * 0.5)
|
|
|
|
|
Talent Moving: (Those are skills too)
|
|
|
|
|
Ayaka: (-1000 initial) (-1500 per second) When the Cryo application at the end of Kamisato Art: Senho hits an opponent (+1000)
|
|
|
|
|
Mona: (-1000 initial) (-1500 per second)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// TODO: Currently only handling Ayaka and Mona's talent moving initial costs.
|
|
|
|
|
Consumption consumption = new Consumption(ConsumptionType.None);
|
|
|
|
|
HashMap<Integer, Integer> fightingCost = new HashMap<>() {{
|
|
|
|
|
put(10013, -1000); // Kamisato Ayaka
|
|
|
|
|
@@ -292,10 +401,12 @@ public class StaminaManager {
|
|
|
|
|
consumption = new Consumption(ConsumptionType.CLIMB_START);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// TODO: Foods
|
|
|
|
|
return consumption;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Consumption getSwimSustainedConsumptions() {
|
|
|
|
|
handleDrowning();
|
|
|
|
|
Consumption consumption = new Consumption(ConsumptionType.None);
|
|
|
|
|
if (currentState == MotionState.MOTION_SWIM_MOVE) {
|
|
|
|
|
consumption = new Consumption(ConsumptionType.SWIMMING);
|
|
|
|
|
@@ -310,6 +421,7 @@ public class StaminaManager {
|
|
|
|
|
Consumption consumption = new Consumption(ConsumptionType.None);
|
|
|
|
|
if (currentState == MotionState.MOTION_DASH) {
|
|
|
|
|
consumption = new Consumption(ConsumptionType.DASH);
|
|
|
|
|
// TODO: Foods
|
|
|
|
|
}
|
|
|
|
|
if (currentState == MotionState.MOTION_RUN) {
|
|
|
|
|
consumption = new Consumption(ConsumptionType.RUN);
|
|
|
|
|
@@ -321,7 +433,12 @@ public class StaminaManager {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Consumption getFlySustainedConsumption() {
|
|
|
|
|
// 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<Integer, Float> glidingCostReduction = new HashMap<>() {{
|
|
|
|
|
put(212301, 0.8f); // Amber
|
|
|
|
|
put(222301, 0.8f); // Venti
|
|
|
|
|
@@ -330,15 +447,15 @@ public class StaminaManager {
|
|
|
|
|
for (EntityAvatar entity : cachedSession.getPlayer().getTeamManager().getActiveTeam()) {
|
|
|
|
|
for (int skillId : entity.getAvatar().getProudSkillList()) {
|
|
|
|
|
if (glidingCostReduction.containsKey(skillId)) {
|
|
|
|
|
reduction = glidingCostReduction.get(skillId);
|
|
|
|
|
float potentialLowerReduction = glidingCostReduction.get(skillId);
|
|
|
|
|
if (potentialLowerReduction < reduction) {
|
|
|
|
|
reduction = potentialLowerReduction;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
consumption.amount *= reduction;
|
|
|
|
|
// POWERED_FLY, e.g. wind tunnel
|
|
|
|
|
if (currentState == MotionState.MOTION_POWERED_FLY) {
|
|
|
|
|
consumption = new Consumption(ConsumptionType.POWERED_FLY);
|
|
|
|
|
}
|
|
|
|
|
// TODO: Foods
|
|
|
|
|
return consumption;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|