mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-02-16 05:22:53 +08:00
Feature: vehicle stamina
1. Remove references. 2. Handle vehicle stamina.
This commit is contained in:
parent
ba5635bf39
commit
2531ae366d
@ -8,5 +8,5 @@ public interface AfterUpdateStaminaListener {
|
|||||||
* @param reason Why updating stamina.
|
* @param reason Why updating stamina.
|
||||||
* @param newStamina New Stamina value.
|
* @param newStamina New Stamina value.
|
||||||
*/
|
*/
|
||||||
void onAfterUpdateStamina(String reason, int newStamina);
|
void onAfterUpdateStamina(String reason, int newStamina, boolean isCharacterStamina);
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ public interface BeforeUpdateStaminaListener {
|
|||||||
* @param newStamina New ABSOLUTE stamina value.
|
* @param newStamina New ABSOLUTE stamina value.
|
||||||
* @return true if you want to cancel this update, otherwise false.
|
* @return true if you want to cancel this update, otherwise false.
|
||||||
*/
|
*/
|
||||||
int onBeforeUpdateStamina(String reason, int newStamina);
|
int onBeforeUpdateStamina(String reason, int newStamina, boolean isCharacterStamina);
|
||||||
/**
|
/**
|
||||||
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina.
|
* onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina.
|
||||||
* This gives listeners a chance to intercept this update.
|
* This gives listeners a chance to intercept this update.
|
||||||
@ -16,5 +16,5 @@ public interface BeforeUpdateStaminaListener {
|
|||||||
* @param consumption ConsumptionType and RELATIVE stamina change amount.
|
* @param consumption ConsumptionType and RELATIVE stamina change amount.
|
||||||
* @return true if you want to cancel this update, otherwise false.
|
* @return true if you want to cancel this update, otherwise false.
|
||||||
*/
|
*/
|
||||||
Consumption onBeforeUpdateStamina(String reason, Consumption consumption);
|
Consumption onBeforeUpdateStamina(String reason, Consumption consumption, boolean isCharacterStamina);
|
||||||
}
|
}
|
@ -13,18 +13,19 @@ public enum ConsumptionType {
|
|||||||
// Slow swimming is handled per movement, not per second.
|
// Slow swimming is handled per movement, not per second.
|
||||||
// Arm movement frequency depends on gender/age/height.
|
// Arm movement frequency depends on gender/age/height.
|
||||||
// TODO: Instead of cost -80 per tick, find a proper way to calculate cost.
|
// TODO: Instead of cost -80 per tick, find a proper way to calculate cost.
|
||||||
SKIFF(-300), // TODO: Get real value
|
SKIFF_DASH(-204),
|
||||||
SPRINT(-1800),
|
SPRINT(-1800),
|
||||||
SWIM_DASH_START(-20),
|
SWIM_DASH_START(-2000),
|
||||||
SWIM_DASH(-204), // -10.2 per second, 5Hz = -204 each tick
|
SWIM_DASH(-204), // -10.2 per second, 5Hz = -204 each tick
|
||||||
SWIMMING(-80),
|
SWIMMING(-80),
|
||||||
TALENT_DASH(-300), // -1500 per second, 5Hz = -300 each tick
|
TALENT_DASH(-300), // -1500 per second, 5Hz = -300 each tick
|
||||||
TALENT_DASH_START(-1000),
|
TALENT_DASH_START(-1000),
|
||||||
|
|
||||||
// restore
|
// restore
|
||||||
POWERED_FLY(500), // TODO: Get real value
|
POWERED_FLY(500),
|
||||||
POWERED_SKIFF(2000), // TODO: Get real value
|
POWERED_SKIFF(500),
|
||||||
RUN(500),
|
RUN(500),
|
||||||
|
SKIFF(500),
|
||||||
STANDBY(500),
|
STANDBY(500),
|
||||||
WALK(500);
|
WALK(500);
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
|
|||||||
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
|
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
|
||||||
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType;
|
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType;
|
||||||
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
|
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
|
||||||
|
import emu.grasscutter.net.proto.VehicleInteractTypeOuterClass.VehicleInteractType;
|
||||||
import emu.grasscutter.server.game.GameSession;
|
import emu.grasscutter.server.game.GameSession;
|
||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
import emu.grasscutter.utils.Position;
|
import emu.grasscutter.utils.Position;
|
||||||
@ -48,7 +49,7 @@ public class StaminaManager {
|
|||||||
)));
|
)));
|
||||||
put("SKIFF", new HashSet<>(List.of(
|
put("SKIFF", new HashSet<>(List.of(
|
||||||
MotionState.MOTION_SKIFF_BOARDING, // NOT OBSERVED even when boarding
|
MotionState.MOTION_SKIFF_BOARDING, // NOT OBSERVED even when boarding
|
||||||
MotionState.MOTION_SKIFF_DASH, // NOT OBSERVED even when dashing
|
MotionState.MOTION_SKIFF_DASH, // sustained, observed with waverider entity ID.
|
||||||
MotionState.MOTION_SKIFF_NORMAL, // sustained, OBSERVED when both normal and dashing
|
MotionState.MOTION_SKIFF_NORMAL, // sustained, OBSERVED when both normal and dashing
|
||||||
MotionState.MOTION_SKIFF_POWERED_DASH // sustained, recover
|
MotionState.MOTION_SKIFF_POWERED_DASH // sustained, recover
|
||||||
)));
|
)));
|
||||||
@ -108,7 +109,8 @@ public class StaminaManager {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
private final Logger logger = Grasscutter.getLogger();
|
private final Logger logger = Grasscutter.getLogger();
|
||||||
public final static int GlobalMaximumStamina = 24000;
|
public final static int GlobalCharacterMaximumStamina = 24000;
|
||||||
|
public final static int GlobalVehicleMaxStamina = 24000;
|
||||||
private Position currentCoordinates = new Position(0, 0, 0);
|
private Position currentCoordinates = new Position(0, 0, 0);
|
||||||
private Position previousCoordinates = new Position(0, 0, 0);
|
private Position previousCoordinates = new Position(0, 0, 0);
|
||||||
private MotionState currentState = MotionState.MOTION_STANDBY;
|
private MotionState currentState = MotionState.MOTION_STANDBY;
|
||||||
@ -122,9 +124,10 @@ public class StaminaManager {
|
|||||||
private int lastSkillId = 0;
|
private int lastSkillId = 0;
|
||||||
private int lastSkillCasterId = 0;
|
private int lastSkillCasterId = 0;
|
||||||
private boolean lastSkillFirstTick = true;
|
private boolean lastSkillFirstTick = true;
|
||||||
|
private int vehicleId = -1;
|
||||||
|
private int vehicleStamina = GlobalVehicleMaxStamina;
|
||||||
private static final HashSet<Integer> TalentMovements = new HashSet<>(List.of(
|
private static final HashSet<Integer> TalentMovements = new HashSet<>(List.of(
|
||||||
10013, // Kamisato Ayaka
|
10013, 10413
|
||||||
10413 // Mona
|
|
||||||
));
|
));
|
||||||
private static final HashMap<Integer, Float> ClimbFoodReductionMap = new HashMap<>() {{
|
private static final HashMap<Integer, Float> ClimbFoodReductionMap = new HashMap<>() {{
|
||||||
// TODO: get real food id
|
// TODO: get real food id
|
||||||
@ -143,15 +146,15 @@ public class StaminaManager {
|
|||||||
put(0, 0.8f); // Sample food
|
put(0, 0.8f); // Sample food
|
||||||
}};
|
}};
|
||||||
private static final HashMap<Integer, Float> ClimbTalentReductionMap = new HashMap<>() {{
|
private static final HashMap<Integer, Float> ClimbTalentReductionMap = new HashMap<>() {{
|
||||||
put(262301, 0.8f); // Xiao
|
put(262301, 0.8f);
|
||||||
}};
|
}};
|
||||||
private static final HashMap<Integer, Float> FlyTalentReductionMap = new HashMap<>() {{
|
private static final HashMap<Integer, Float> FlyTalentReductionMap = new HashMap<>() {{
|
||||||
put(212301, 0.8f); // Amber
|
put(212301, 0.8f);
|
||||||
put(222301, 0.8f); // Venti
|
put(222301, 0.8f);
|
||||||
}};
|
}};
|
||||||
private static final HashMap<Integer, Float> SwimTalentReductionMap = new HashMap<>() {{
|
private static final HashMap<Integer, Float> SwimTalentReductionMap = new HashMap<>() {{
|
||||||
put(242301, 0.8f); // Beidou
|
put(242301, 0.8f);
|
||||||
put(542301, 0.8f); // Sangonomiya Kokomi
|
put(542301, 0.8f);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
public static final HashSet<Integer> BowAvatars = new HashSet<>();
|
public static final HashSet<Integer> BowAvatars = new HashSet<>();
|
||||||
@ -163,25 +166,15 @@ public class StaminaManager {
|
|||||||
public static void initialize() {
|
public static void initialize() {
|
||||||
// Initialize skill categories
|
// Initialize skill categories
|
||||||
GameData.getAvatarDataMap().forEach((avatarId, avatarData) -> {
|
GameData.getAvatarDataMap().forEach((avatarId, avatarData) -> {
|
||||||
switch(avatarData.getWeaponType()) {
|
switch (avatarData.getWeaponType()) {
|
||||||
case "WEAPON_BOW":
|
case "WEAPON_BOW" -> BowAvatars.add(avatarId);
|
||||||
BowAvatars.add(avatarId);
|
case "WEAPON_CLAYMORE" -> ClaymoreAvatars.add(avatarId);
|
||||||
break;
|
case "WEAPON_CATALYST" -> CatalystAvatars.add(avatarId);
|
||||||
case "WEAPON_CLAYMORE":
|
case "WEAPON_POLE" -> PolearmAvatars.add(avatarId);
|
||||||
ClaymoreAvatars.add(avatarId);
|
case "WEAPON_SWORD_ONE_HAND" -> SwordAvatars.add(avatarId);
|
||||||
break;
|
|
||||||
case "WEAPON_CATALYST":
|
|
||||||
CatalystAvatars.add(avatarId);
|
|
||||||
break;
|
|
||||||
case "WEAPON_POLE":
|
|
||||||
PolearmAvatars.add(avatarId);
|
|
||||||
break;
|
|
||||||
case "WEAPON_SWORD_ONE_HAND":
|
|
||||||
SwordAvatars.add(avatarId);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
// TODO: Initialize foods etc.
|
|
||||||
});
|
});
|
||||||
|
// TODO: Initialize foods etc.
|
||||||
}
|
}
|
||||||
|
|
||||||
public StaminaManager(Player player) {
|
public StaminaManager(Player player) {
|
||||||
@ -196,6 +189,22 @@ public class StaminaManager {
|
|||||||
lastSkillCasterId = skillCasterId;
|
lastSkillCasterId = skillCasterId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMaxCharacterStamina() {
|
||||||
|
return player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentCharacterStamina() {
|
||||||
|
return player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxVehicleStamina() {
|
||||||
|
return GlobalVehicleMaxStamina;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentVehicleStamina() {
|
||||||
|
return vehicleStamina;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean registerBeforeUpdateStaminaListener(String listenerName, BeforeUpdateStaminaListener listener) {
|
public boolean registerBeforeUpdateStaminaListener(String listenerName, BeforeUpdateStaminaListener listener) {
|
||||||
if (beforeUpdateStaminaListeners.containsKey(listenerName)) {
|
if (beforeUpdateStaminaListeners.containsKey(listenerName)) {
|
||||||
return false;
|
return false;
|
||||||
@ -237,14 +246,14 @@ public class StaminaManager {
|
|||||||
return Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.2 || Math.abs(diffZ) > 0.3;
|
return Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.2 || Math.abs(diffZ) > 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int updateStaminaRelative(GameSession session, Consumption consumption, PlayerProperty staminaType) {
|
public int updateStaminaRelative(GameSession session, Consumption consumption, boolean isCharacterStamina) {
|
||||||
int currentStamina = player.getProperty(staminaType);
|
int currentStamina = isCharacterStamina ? getCurrentCharacterStamina() : getCurrentVehicleStamina();
|
||||||
if (consumption.amount == 0) {
|
if (consumption.amount == 0) {
|
||||||
return currentStamina;
|
return currentStamina;
|
||||||
}
|
}
|
||||||
// notify will update
|
// notify will update
|
||||||
for (Map.Entry<String, BeforeUpdateStaminaListener> listener : beforeUpdateStaminaListeners.entrySet()) {
|
for (Map.Entry<String, BeforeUpdateStaminaListener> listener : beforeUpdateStaminaListeners.entrySet()) {
|
||||||
Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.type.toString(), consumption);
|
Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.type.toString(), consumption, isCharacterStamina);
|
||||||
if ((overriddenConsumption.type != consumption.type) && (overriddenConsumption.amount != consumption.amount)) {
|
if ((overriddenConsumption.type != consumption.type) && (overriddenConsumption.amount != consumption.amount)) {
|
||||||
logger.debug("Stamina update relative(" +
|
logger.debug("Stamina update relative(" +
|
||||||
consumption.type.toString() + ", " + consumption.amount + ") overridden to relative(" +
|
consumption.type.toString() + ", " + consumption.amount + ") overridden to relative(" +
|
||||||
@ -252,24 +261,24 @@ public class StaminaManager {
|
|||||||
return currentStamina;
|
return currentStamina;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
int maxStamina = isCharacterStamina ? getMaxCharacterStamina() : getMaxVehicleStamina();
|
||||||
logger.trace(currentStamina + "/" + playerMaxStamina + "\t" + currentState + "\t" +
|
logger.warn((isCharacterStamina ? "C " : "V ") + currentStamina + "/" + maxStamina + "\t" + currentState + "\t" +
|
||||||
(isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.type + "," +
|
(isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.type + "," +
|
||||||
consumption.amount + ")");
|
consumption.amount + ")");
|
||||||
int newStamina = currentStamina + consumption.amount;
|
int newStamina = currentStamina + consumption.amount;
|
||||||
if (newStamina < 0) {
|
if (newStamina < 0) {
|
||||||
newStamina = 0;
|
newStamina = 0;
|
||||||
} else if (newStamina > playerMaxStamina) {
|
} else if (newStamina > maxStamina) {
|
||||||
newStamina = playerMaxStamina;
|
newStamina = maxStamina;
|
||||||
}
|
}
|
||||||
return setStamina(session, consumption.type.toString(), newStamina, staminaType);
|
return setStamina(session, consumption.type.toString(), newStamina, isCharacterStamina);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int updateStaminaAbsolute(GameSession session, String reason, int newStamina, PlayerProperty staminaType) {
|
public int updateStaminaAbsolute(GameSession session, String reason, int newStamina, boolean isCharacterStamina) {
|
||||||
int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
int currentStamina = isCharacterStamina ? getCurrentCharacterStamina() : getCurrentVehicleStamina();
|
||||||
// notify will update
|
// notify will update
|
||||||
for (Map.Entry<String, BeforeUpdateStaminaListener> listener : beforeUpdateStaminaListeners.entrySet()) {
|
for (Map.Entry<String, BeforeUpdateStaminaListener> listener : beforeUpdateStaminaListeners.entrySet()) {
|
||||||
int overriddenNewStamina = listener.getValue().onBeforeUpdateStamina(reason, newStamina);
|
int overriddenNewStamina = listener.getValue().onBeforeUpdateStamina(reason, newStamina, isCharacterStamina);
|
||||||
if (overriddenNewStamina != newStamina) {
|
if (overriddenNewStamina != newStamina) {
|
||||||
logger.debug("Stamina update absolute(" +
|
logger.debug("Stamina update absolute(" +
|
||||||
reason + ", " + newStamina + ") overridden to absolute(" +
|
reason + ", " + newStamina + ") overridden to absolute(" +
|
||||||
@ -277,31 +286,31 @@ public class StaminaManager {
|
|||||||
return currentStamina;
|
return currentStamina;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
int maxStamina = isCharacterStamina ? getMaxCharacterStamina() : getMaxVehicleStamina();
|
||||||
if (newStamina < 0) {
|
if (newStamina < 0) {
|
||||||
newStamina = 0;
|
newStamina = 0;
|
||||||
} else if (newStamina > playerMaxStamina) {
|
} else if (newStamina > maxStamina) {
|
||||||
newStamina = playerMaxStamina;
|
newStamina = maxStamina;
|
||||||
}
|
}
|
||||||
return setStamina(session, reason, newStamina, staminaType);
|
return setStamina(session, reason, newStamina, isCharacterStamina);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns new stamina and sends PlayerPropNotify
|
// Returns new stamina and sends PlayerPropNotify or VehicleStaminaNotify
|
||||||
public int setStamina(GameSession session, String reason, int newStamina, PlayerProperty staminaType) {
|
public int setStamina(GameSession session, String reason, int newStamina, boolean isCharacterStamina) {
|
||||||
if (!GAME_OPTIONS.staminaUsage) {
|
if (!GAME_OPTIONS.staminaUsage) {
|
||||||
newStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
newStamina = getMaxCharacterStamina();
|
||||||
}
|
}
|
||||||
// set stamina
|
// set stamina if is character stamina
|
||||||
player.setProperty(staminaType, newStamina);
|
if (isCharacterStamina) {
|
||||||
if (staminaType == PlayerProperty.PROP_CUR_TEMPORARY_STAMINA) {
|
player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina);
|
||||||
// TODO: Implement
|
|
||||||
// session.send(new PacketVehicleStaminaNotify(vehicleEntity, newStamina));
|
|
||||||
} else {
|
|
||||||
session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA));
|
session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA));
|
||||||
|
} else {
|
||||||
|
vehicleStamina = newStamina;
|
||||||
|
session.send(new PacketVehicleStaminaNotify(vehicleId, ((float) newStamina) / 100));
|
||||||
}
|
}
|
||||||
// notify updated
|
// notify updated
|
||||||
for (Map.Entry<String, AfterUpdateStaminaListener> listener : afterUpdateStaminaListeners.entrySet()) {
|
for (Map.Entry<String, AfterUpdateStaminaListener> listener : afterUpdateStaminaListeners.entrySet()) {
|
||||||
listener.getValue().onAfterUpdateStamina(reason, newStamina);
|
listener.getValue().onAfterUpdateStamina(reason, newStamina, isCharacterStamina);
|
||||||
}
|
}
|
||||||
return newStamina;
|
return newStamina;
|
||||||
}
|
}
|
||||||
@ -379,11 +388,11 @@ public class StaminaManager {
|
|||||||
MotionState motionState = motionInfo.getState();
|
MotionState motionState = motionInfo.getState();
|
||||||
int notifyEntityId = entity.getId();
|
int notifyEntityId = entity.getId();
|
||||||
int currentAvatarEntityId = session.getPlayer().getTeamManager().getCurrentAvatarEntity().getId();
|
int currentAvatarEntityId = session.getPlayer().getTeamManager().getCurrentAvatarEntity().getId();
|
||||||
if (notifyEntityId != currentAvatarEntityId) {
|
if (notifyEntityId != currentAvatarEntityId && notifyEntityId != vehicleId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentState = motionState;
|
currentState = motionState;
|
||||||
// logger.trace("" + currentState);
|
// logger.trace(currentState + "\t" + (notifyEntityId == currentAvatarEntityId ? "character" : "vehicle"));
|
||||||
Vector posVector = motionInfo.getPos();
|
Vector posVector = motionInfo.getPos();
|
||||||
Position newPos = new Position(posVector.getX(), posVector.getY(), posVector.getZ());
|
Position newPos = new Position(posVector.getX(), posVector.getY(), posVector.getZ());
|
||||||
if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) {
|
if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) {
|
||||||
@ -393,28 +402,40 @@ public class StaminaManager {
|
|||||||
handleImmediateStamina(session, motionState);
|
handleImmediateStamina(session, motionState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void handleVehicleInteractReq(GameSession session, int vehicleId, VehicleInteractType vehicleInteractType) {
|
||||||
|
if (vehicleInteractType == VehicleInteractType.VEHICLE_INTERACT_IN) {
|
||||||
|
this.vehicleId = vehicleId;
|
||||||
|
// Reset character stamina here to prevent falling into water immediately on ejection if char stamina is
|
||||||
|
// close to empty when boarding.
|
||||||
|
updateStaminaAbsolute(session, "board vehicle", getMaxCharacterStamina(), true);
|
||||||
|
updateStaminaAbsolute(session, "board vehicle", getMaxVehicleStamina(), false);
|
||||||
|
} else {
|
||||||
|
this.vehicleId = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Internal handler
|
// Internal handler
|
||||||
|
|
||||||
private void handleImmediateStamina(GameSession session, @NotNull MotionState motionState) {
|
private void handleImmediateStamina(GameSession session, @NotNull MotionState motionState) {
|
||||||
switch (motionState) {
|
switch (motionState) {
|
||||||
case MOTION_CLIMB:
|
case MOTION_CLIMB:
|
||||||
if (currentState != MotionState.MOTION_CLIMB) {
|
if (currentState != MotionState.MOTION_CLIMB) {
|
||||||
updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START), PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START), true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MOTION_DASH_BEFORE_SHAKE:
|
case MOTION_DASH_BEFORE_SHAKE:
|
||||||
if (previousState != MotionState.MOTION_DASH_BEFORE_SHAKE) {
|
if (previousState != MotionState.MOTION_DASH_BEFORE_SHAKE) {
|
||||||
updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT), PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT), true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MOTION_CLIMB_JUMP:
|
case MOTION_CLIMB_JUMP:
|
||||||
if (previousState != MotionState.MOTION_CLIMB_JUMP) {
|
if (previousState != MotionState.MOTION_CLIMB_JUMP) {
|
||||||
updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP), PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP), true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case MOTION_SWIM_DASH:
|
case MOTION_SWIM_DASH:
|
||||||
if (previousState != MotionState.MOTION_SWIM_DASH) {
|
if (previousState != MotionState.MOTION_SWIM_DASH) {
|
||||||
updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START), PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START), true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -422,18 +443,20 @@ public class StaminaManager {
|
|||||||
|
|
||||||
private void handleImmediateStamina(GameSession session, int skillId) {
|
private void handleImmediateStamina(GameSession session, int skillId) {
|
||||||
Consumption consumption = getFightConsumption(skillId);
|
Consumption consumption = getFightConsumption(skillId);
|
||||||
updateStaminaRelative(session, consumption, PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
updateStaminaRelative(session, consumption, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SustainedStaminaHandler extends TimerTask {
|
private class SustainedStaminaHandler extends TimerTask {
|
||||||
public void run() {
|
public void run() {
|
||||||
boolean moving = isPlayerMoving();
|
boolean moving = isPlayerMoving();
|
||||||
int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
int currentCharacterStamina = getCurrentCharacterStamina();
|
||||||
int maxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
int maxCharacterStamina = getMaxCharacterStamina();
|
||||||
if (moving || (currentStamina < maxStamina)) {
|
int currentVehicleStamina = getCurrentVehicleStamina();
|
||||||
|
int maxVehicleStamina = getMaxVehicleStamina();
|
||||||
|
if (moving || (currentCharacterStamina < maxCharacterStamina) || (currentVehicleStamina < maxVehicleStamina)) {
|
||||||
logger.trace("Player moving: " + moving + ", stamina full: " +
|
logger.trace("Player moving: " + moving + ", stamina full: " +
|
||||||
(currentStamina >= maxStamina) + ", recalculate stamina");
|
(currentCharacterStamina >= maxCharacterStamina) + ", recalculate stamina");
|
||||||
|
boolean isCharacterStamina = true;
|
||||||
Consumption consumption;
|
Consumption consumption;
|
||||||
if (MotionStatesCategorized.get("CLIMB").contains(currentState)) {
|
if (MotionStatesCategorized.get("CLIMB").contains(currentState)) {
|
||||||
consumption = getClimbConsumption();
|
consumption = getClimbConsumption();
|
||||||
@ -445,6 +468,7 @@ public class StaminaManager {
|
|||||||
consumption = new Consumption(ConsumptionType.RUN);
|
consumption = new Consumption(ConsumptionType.RUN);
|
||||||
} else if (MotionStatesCategorized.get("SKIFF").contains(currentState)) {
|
} else if (MotionStatesCategorized.get("SKIFF").contains(currentState)) {
|
||||||
consumption = getSkiffConsumption();
|
consumption = getSkiffConsumption();
|
||||||
|
isCharacterStamina = false;
|
||||||
} else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) {
|
} else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) {
|
||||||
consumption = new Consumption(ConsumptionType.STANDBY);
|
consumption = new Consumption(ConsumptionType.STANDBY);
|
||||||
} else if (MotionStatesCategorized.get("SWIM").contains(currentState)) {
|
} else if (MotionStatesCategorized.get("SWIM").contains(currentState)) {
|
||||||
@ -459,16 +483,10 @@ public class StaminaManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (consumption.amount < 0) {
|
if (consumption.amount < 0 && isCharacterStamina) {
|
||||||
/* Do not apply reduction factor when recovering stamina
|
// Do not apply reduction factor when recovering stamina
|
||||||
TODO: Reductions that apply to all motion types:
|
|
||||||
Skills
|
|
||||||
Diona E: -10% while shield lasts - applies to SP+MP
|
|
||||||
Barbara E: -12% while lasts - applies to SP+MP
|
|
||||||
*/
|
|
||||||
// Elemental Resonance - Winds -15%
|
|
||||||
if (player.getTeamManager().getTeamResonances().contains(10301)) {
|
if (player.getTeamManager().getTeamResonances().contains(10301)) {
|
||||||
consumption.amount *= 0.85f;
|
consumption.amount *= 0.85f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Delay 1 seconds before starts recovering stamina
|
// Delay 1 seconds before starts recovering stamina
|
||||||
@ -476,8 +494,10 @@ public class StaminaManager {
|
|||||||
if (consumption.amount < 0) {
|
if (consumption.amount < 0) {
|
||||||
staminaRecoverDelay = 0;
|
staminaRecoverDelay = 0;
|
||||||
}
|
}
|
||||||
if (consumption.amount > 0 && consumption.type != ConsumptionType.POWERED_FLY) {
|
if (consumption.amount > 0
|
||||||
// For POWERED_FLY recover immediately - things like Amber's gliding exam may require this.
|
&& consumption.type != ConsumptionType.POWERED_FLY
|
||||||
|
&& consumption.type != ConsumptionType.POWERED_SKIFF) {
|
||||||
|
// For POWERED_* recover immediately - things like Amber's gliding exam and skiff challenges may require this.
|
||||||
if (staminaRecoverDelay < 5) {
|
if (staminaRecoverDelay < 5) {
|
||||||
// For others recover after 1 seconds (5 ticks) - as official server does.
|
// For others recover after 1 seconds (5 ticks) - as official server does.
|
||||||
staminaRecoverDelay++;
|
staminaRecoverDelay++;
|
||||||
@ -485,7 +505,7 @@ public class StaminaManager {
|
|||||||
logger.trace("Delaying recovery: " + staminaRecoverDelay);
|
logger.trace("Delaying recovery: " + staminaRecoverDelay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateStaminaRelative(cachedSession, consumption, PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
updateStaminaRelative(cachedSession, consumption, isCharacterStamina);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
previousState = currentState;
|
previousState = currentState;
|
||||||
@ -499,10 +519,10 @@ public class StaminaManager {
|
|||||||
|
|
||||||
private void handleDrowning() {
|
private void handleDrowning() {
|
||||||
// TODO: fix drowning waverider entity
|
// TODO: fix drowning waverider entity
|
||||||
int stamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA);
|
int stamina = getCurrentCharacterStamina();
|
||||||
if (stamina < 10) {
|
if (stamina < 10) {
|
||||||
logger.trace(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" +
|
logger.trace(getCurrentCharacterStamina() + "/" +
|
||||||
player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState);
|
getMaxCharacterStamina() + "\t" + currentState);
|
||||||
if (currentState != MotionState.MOTION_SWIM_IDLE) {
|
if (currentState != MotionState.MOTION_SWIM_IDLE) {
|
||||||
killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_DRAWN);
|
killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_DRAWN);
|
||||||
}
|
}
|
||||||
@ -530,15 +550,15 @@ public class StaminaManager {
|
|||||||
}
|
}
|
||||||
// Catalyst avatar charged attack
|
// Catalyst avatar charged attack
|
||||||
if (CatalystAvatars.contains(currentAvatarId)) {
|
if (CatalystAvatars.contains(currentAvatarId)) {
|
||||||
return getCatalystSustainedCost(skillCasting);
|
return getCatalystCost(skillCasting);
|
||||||
}
|
}
|
||||||
// Polearm avatar charged attack
|
// Polearm avatar charged attack
|
||||||
if (PolearmAvatars.contains(currentAvatarId)) {
|
if (PolearmAvatars.contains(currentAvatarId)) {
|
||||||
return getPolearmSustainedCost(skillCasting);
|
return getPolearmCost(skillCasting);
|
||||||
}
|
}
|
||||||
// Sword avatar charged attack
|
// Sword avatar charged attack
|
||||||
if (SwordAvatars.contains(skillCasting)) {
|
if (SwordAvatars.contains(skillCasting)) {
|
||||||
return getSwordSustainedCost(skillCasting);
|
return getSwordCost(skillCasting);
|
||||||
}
|
}
|
||||||
return new Consumption();
|
return new Consumption();
|
||||||
}
|
}
|
||||||
@ -596,12 +616,13 @@ public class StaminaManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Consumption getSkiffConsumption() {
|
private Consumption getSkiffConsumption() {
|
||||||
// POWERED_SKIFF, e.g. wind tunnel
|
|
||||||
if (currentState == MotionState.MOTION_SKIFF_POWERED_DASH) {
|
|
||||||
return new Consumption(ConsumptionType.POWERED_SKIFF);
|
|
||||||
}
|
|
||||||
// No known reduction for skiffing.
|
// No known reduction for skiffing.
|
||||||
return new Consumption(ConsumptionType.SKIFF);
|
return switch (currentState) {
|
||||||
|
case MOTION_SKIFF_DASH -> new Consumption(ConsumptionType.SKIFF_DASH);
|
||||||
|
case MOTION_SKIFF_POWERED_DASH -> new Consumption(ConsumptionType.POWERED_SKIFF);
|
||||||
|
case MOTION_SKIFF_NORMAL -> new Consumption(ConsumptionType.SKIFF);
|
||||||
|
default -> new Consumption();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Consumption getOtherConsumptions() {
|
private Consumption getOtherConsumptions() {
|
||||||
@ -662,11 +683,11 @@ public class StaminaManager {
|
|||||||
return new Consumption(ConsumptionType.FIGHT, +500);
|
return new Consumption(ConsumptionType.FIGHT, +500);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Consumption getCatalystSustainedCost(int skillId) {
|
private Consumption getCatalystCost(int skillId) {
|
||||||
Consumption consumption = new Consumption(ConsumptionType.FIGHT, -5000);
|
Consumption consumption = new Consumption(ConsumptionType.FIGHT, -5000);
|
||||||
// Character specific handling
|
// Character specific handling
|
||||||
switch (skillId) {
|
switch (skillId) {
|
||||||
// TODO: Yanfei
|
// TODO:
|
||||||
}
|
}
|
||||||
return consumption;
|
return consumption;
|
||||||
}
|
}
|
||||||
@ -675,11 +696,11 @@ public class StaminaManager {
|
|||||||
Consumption consumption = new Consumption(ConsumptionType.FIGHT, -1333); // 4000 / 3 = 1333
|
Consumption consumption = new Consumption(ConsumptionType.FIGHT, -1333); // 4000 / 3 = 1333
|
||||||
// Character specific handling
|
// Character specific handling
|
||||||
switch (skillId) {
|
switch (skillId) {
|
||||||
case 10571: // Arataki Itto, does not consume stamina at all.
|
case 10571:
|
||||||
case 10532: // Sayu, windwheel does not consume stamina.
|
case 10532:
|
||||||
consumption.amount = 0;
|
consumption.amount = 0;
|
||||||
break;
|
break;
|
||||||
case 10160: // Diluc, with talent "Relentless" stamina cost is decreased by 50%
|
case 10160:
|
||||||
if (player.getTeamManager().getCurrentAvatarEntity().getAvatar().getProudSkillList().contains(162101)) {
|
if (player.getTeamManager().getCurrentAvatarEntity().getAvatar().getProudSkillList().contains(162101)) {
|
||||||
consumption.amount /= 2;
|
consumption.amount /= 2;
|
||||||
}
|
}
|
||||||
@ -688,7 +709,7 @@ public class StaminaManager {
|
|||||||
return consumption;
|
return consumption;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Consumption getPolearmSustainedCost(int skillId) {
|
private Consumption getPolearmCost(int skillId) {
|
||||||
Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2500);
|
Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2500);
|
||||||
// Character specific handling
|
// Character specific handling
|
||||||
switch (skillId) {
|
switch (skillId) {
|
||||||
@ -697,11 +718,11 @@ public class StaminaManager {
|
|||||||
return consumption;
|
return consumption;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Consumption getSwordSustainedCost(int skillId) {
|
private Consumption getSwordCost(int skillId) {
|
||||||
Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2000);
|
Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2000);
|
||||||
// Character specific handling
|
// Character specific handling
|
||||||
switch (skillId) {
|
switch (skillId) {
|
||||||
case 10421: // Keqing, -2500
|
case 10421:
|
||||||
consumption.amount = -2500;
|
consumption.amount = -2500;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1237,7 +1237,7 @@ public class Player {
|
|||||||
} else if (prop == PlayerProperty.PROP_IS_TRANSFERABLE) { // 10009
|
} else if (prop == PlayerProperty.PROP_IS_TRANSFERABLE) { // 10009
|
||||||
if (!(0 <= value && value <= 1)) { return false; }
|
if (!(0 <= value && value <= 1)) { return false; }
|
||||||
} else if (prop == PlayerProperty.PROP_MAX_STAMINA) { // 10010
|
} else if (prop == PlayerProperty.PROP_MAX_STAMINA) { // 10010
|
||||||
if (!(value >= 0 && value <= StaminaManager.GlobalMaximumStamina)) { return false; }
|
if (!(value >= 0 && value <= StaminaManager.GlobalCharacterMaximumStamina)) { return false; }
|
||||||
} else if (prop == PlayerProperty.PROP_CUR_PERSIST_STAMINA) { // 10011
|
} else if (prop == PlayerProperty.PROP_CUR_PERSIST_STAMINA) { // 10011
|
||||||
int playerMaximumStamina = getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
int playerMaximumStamina = getProperty(PlayerProperty.PROP_MAX_STAMINA);
|
||||||
if (!(value >= 0 && value <= playerMaximumStamina)) { return false; }
|
if (!(value >= 0 && value <= playerMaximumStamina)) { return false; }
|
||||||
|
@ -14,6 +14,7 @@ public class HandlerVehicleInteractReq extends PacketHandler {
|
|||||||
@Override
|
@Override
|
||||||
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
|
||||||
VehicleInteractReqOuterClass.VehicleInteractReq req = VehicleInteractReqOuterClass.VehicleInteractReq.parseFrom(payload);
|
VehicleInteractReqOuterClass.VehicleInteractReq req = VehicleInteractReqOuterClass.VehicleInteractReq.parseFrom(payload);
|
||||||
|
session.getPlayer().getStaminaManager().handleVehicleInteractReq(session, req.getEntityId(), req.getInteractType());
|
||||||
session.send(new PacketVehicleInteractRsp(session.getPlayer(), req.getEntityId(), req.getInteractType()));
|
session.send(new PacketVehicleInteractRsp(session.getPlayer(), req.getEntityId(), req.getInteractType()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
package emu.grasscutter.server.packet.send;
|
package emu.grasscutter.server.packet.send;
|
||||||
|
|
||||||
import emu.grasscutter.game.entity.GameEntity;
|
|
||||||
import emu.grasscutter.net.packet.BasePacket;
|
import emu.grasscutter.net.packet.BasePacket;
|
||||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||||
import emu.grasscutter.net.proto.VehicleStaminaNotifyOuterClass.VehicleStaminaNotify;
|
import emu.grasscutter.net.proto.VehicleStaminaNotifyOuterClass.VehicleStaminaNotify;
|
||||||
|
|
||||||
public class PacketVehicleStaminaNotify extends BasePacket {
|
public class PacketVehicleStaminaNotify extends BasePacket {
|
||||||
|
|
||||||
public PacketVehicleStaminaNotify(GameEntity entity, int newStamina) {
|
public PacketVehicleStaminaNotify(int vehicleId, float newStamina) {
|
||||||
super(PacketOpcodes.VehicleStaminaNotify);
|
super(PacketOpcodes.VehicleStaminaNotify);
|
||||||
VehicleStaminaNotify.Builder proto = VehicleStaminaNotify.newBuilder();
|
VehicleStaminaNotify.Builder proto = VehicleStaminaNotify.newBuilder();
|
||||||
|
|
||||||
proto.setEntityId(entity.getId());
|
proto.setEntityId(vehicleId);
|
||||||
proto.setCurStamina(newStamina);
|
proto.setCurStamina(newStamina);
|
||||||
|
|
||||||
this.setData(proto.build());
|
this.setData(proto.build());
|
||||||
|
Loading…
Reference in New Issue
Block a user