Commit d1d39db5 authored by AnimeGitB's avatar AnimeGitB
Browse files

[BREAKING] Item Usage Overhaul

-De-hardcode elemental orb values
-De-hardcode exp items
-Change ShopChest format (temporary, drop system overhaul will replace it entirely)
-Food healing actually uses Ability data for real HP amounts
parent 5bb43ac0
......@@ -25,10 +25,12 @@ public class GameData {
// BinOutputs
@Getter private static final Int2ObjectMap<HomeworldDefaultSaveData> homeworldDefaultSaveData = new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<String> abilityHashes = new Int2ObjectOpenHashMap<>();
@Deprecated(forRemoval = true)
@Getter private static final Map<String, AbilityModifierEntry> abilityModifiers = new HashMap<>();
@Getter private static final Map<String, ConfigGadget> gadgetConfigData = new HashMap<>();
@Getter private static final Map<String, OpenConfigEntry> openConfigEntries = new HashMap<>();
@Deprecated(forRemoval = true) @Getter private static final Map<String, ScenePointEntry> scenePointEntries = new HashMap<>();
protected static final Map<String, AbilityData> abilityDataMap = new HashMap<>();
protected static final Int2ObjectMap<ScenePointEntry> scenePointEntryMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<MainQuestData> mainQuestData = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<QuestEncryptionKey> questsKeys = new Int2ObjectOpenHashMap<>();
......@@ -136,6 +138,7 @@ public class GameData {
public static Map<String, AbilityEmbryoEntry> getAbilityEmbryoInfo() {return abilityEmbryos;}
// Getters that get values rather than containers. If Lombok ever gets syntactic sugar for this, we should adopt that.
public static AbilityData getAbilityData(String abilityName) {return abilityDataMap.get(abilityName);}
public static IntSet getAvatarSkillLevels(int avatarSkillId) {return avatarSkillLevels.get(avatarSkillId);}
public static IntSet getProudSkillGroupLevels(int proudSkillGroupId) {return proudSkillGroupLevels.get(proudSkillGroupId);}
......
......@@ -3,8 +3,7 @@ package emu.grasscutter.data;
import com.google.gson.annotations.SerializedName;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.binout.*;
import emu.grasscutter.data.binout.AbilityModifier.AbilityConfigData;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierActionType;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.data.common.PointData;
import emu.grasscutter.game.managers.blossom.BlossomConfig;
import emu.grasscutter.game.quest.QuestEncryptionKey;
......@@ -22,6 +21,7 @@ import org.reflections.Reflections;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Stream;
......@@ -237,57 +237,54 @@ public class ResourceLoader {
}
}
// private static HashSet<String> modifierActionTypes = new HashSet<>();
public static class AbilityConfigData {
public AbilityData Default;
}
private static void loadAbilityModifiers() {
// Load from BinOutput
try {
Files.newDirectoryStream(getResourcePath("BinOutput/Ability/Temp/AvatarAbilities/")).forEach(path -> {
List<AbilityConfigData> abilityConfigList;
try {
abilityConfigList = JsonUtils.loadToList(path, AbilityConfigData.class);
} catch (IOException e) {
Grasscutter.getLogger().error("Error loading ability modifiers from path " + path.toString() + ": ", e);
return;
}
abilityConfigList.forEach(data -> {
if (data.Default.modifiers == null || data.Default.modifiers.size() == 0) {
return;
}
String name = data.Default.abilityName;
AbilityModifierEntry modifierEntry = new AbilityModifierEntry(name);
data.Default.modifiers.forEach((key, modifier) -> {
Stream.ofNullable(modifier.onAdded)
.flatMap(Stream::of)
.filter(action -> action.$type.contains("HealHP"))
.forEach(action -> {
action.type = AbilityModifierActionType.HealHP;
modifierEntry.getOnAdded().add(action);
});
Stream.ofNullable(modifier.onThinkInterval)
.flatMap(Stream::of)
.filter(action -> action.$type.contains("HealHP"))
.forEach(action -> {
action.type = AbilityModifierActionType.HealHP;
modifierEntry.getOnThinkInterval().add(action);
});
Stream.ofNullable(modifier.onRemoved)
.flatMap(Stream::of)
.filter(action -> action.$type.contains("HealHP"))
.forEach(action -> {
action.type = AbilityModifierActionType.HealHP;
modifierEntry.getOnRemoved().add(action);
});
});
GameData.getAbilityModifiers().put(name, modifierEntry);
});
});
try (Stream<Path> paths = Files.walk(getResourcePath("BinOutput/Ability/Temp/"))) {
paths.filter(Files::isRegularFile).filter(path -> path.toString().endsWith(".json")).forEach(ResourceLoader::loadAbilityModifiers);
} catch (IOException e) {
Grasscutter.getLogger().error("Error loading ability modifiers: ", e);
return;
}
// System.out.println("Loaded modifiers, found types:");
// modifierActionTypes.stream().sorted().forEach(s -> System.out.printf("%s, ", s));
// System.out.println("[End]");
}
private static void loadAbilityModifiers(Path path) {
try {
JsonUtils.loadToList(path, AbilityConfigData.class).forEach(data -> loadAbilityData(data.Default));
} catch (IOException e) {
Grasscutter.getLogger().error("Error loading ability modifiers from path " + path.toString() + ": ", e);
return;
}
}
private static void loadAbilityData(AbilityData data) {
GameData.abilityDataMap.put(data.abilityName, data);
val modifiers = data.modifiers;
if (modifiers == null || modifiers.size() == 0) return;
String name = data.abilityName;
AbilityModifierEntry modifierEntry = new AbilityModifierEntry(name);
modifiers.forEach((key, modifier) -> {
Stream.ofNullable(modifier.onAdded).flatMap(Stream::of)
// .map(action -> {modifierActionTypes.add(action.$type); return action;})
.filter(action -> action.type == AbilityModifierAction.Type.HealHP)
.forEach(action -> modifierEntry.getOnAdded().add(action));
Stream.ofNullable(modifier.onThinkInterval).flatMap(Stream::of)
// .map(action -> {modifierActionTypes.add(action.$type); return action;})
.filter(action -> action.type == AbilityModifierAction.Type.HealHP)
.forEach(action -> modifierEntry.getOnThinkInterval().add(action));
Stream.ofNullable(modifier.onRemoved).flatMap(Stream::of)
// .map(action -> {modifierActionTypes.add(action.$type); return action;})
.filter(action -> action.type == AbilityModifierAction.Type.HealHP)
.forEach(action -> modifierEntry.getOnRemoved().add(action));
});
GameData.getAbilityModifiers().put(name, modifierEntry);
}
private static void loadSpawnData() {
......
package emu.grasscutter.data.binout;
import java.util.Map;
public class AbilityData {
public String abilityName;
public Map<String, AbilityModifier> modifiers;
public boolean isDynamicAbility;
public Map<String, Float> abilitySpecials;
// abilityMixins
// onAbilityStart
// onKill
}
package emu.grasscutter.data.binout;
import java.util.Map;
import java.io.Serializable;
import com.google.gson.annotations.SerializedName;
import emu.grasscutter.data.common.DynamicFloat;
public class AbilityModifier implements Serializable {
private static final long serialVersionUID = -2001232313615923575L;
private static final long serialVersionUID = -2001232313615923575L;
@SerializedName(value="onAdded", alternate={"KCICDEJLIJD"})
public AbilityModifierAction[] onAdded;
@SerializedName(value="onThinkInterval", alternate={"PBDDACFFPOE"})
public AbilityModifierAction[] onThinkInterval;
public AbilityModifierAction[] onRemoved;
public DynamicFloat duration = DynamicFloat.ZERO;
@SerializedName(value="onAdded", alternate={"KCICDEJLIJD"})
public AbilityModifierAction[] onAdded;
@SerializedName(value="onThinkInterval", alternate={"PBDDACFFPOE"})
public AbilityModifierAction[] onThinkInterval;
public AbilityModifierAction[] onRemoved;
public static class AbilityModifierAction {
public enum Type {
ActCameraRadialBlur, ActCameraShake, AddAvatarSkillInfo, AddChargeBarValue,
AddClimateMeter, AddElementDurability, AddGlobalValue, AddGlobalValueToTarget,
AddRegionalPlayVarValue, ApplyModifier, AttachAbilityStateResistance, AttachBulletAimPoint,
AttachEffect, AttachEffectFirework, AttachElementTypeResistance, AttachModifier,
AttachUIEffect, AvatarCameraParam, AvatarEnterCameraShot, AvatarEnterFocus,
AvatarEnterViewBias, AvatarExitCameraShot, AvatarExitClimb, AvatarExitFocus,
AvatarExitViewBias, AvatarShareCDSkillStart, AvatarSkillStart, BroadcastNeuronStimulate,
CalcDvalinS04RebornPoint, CallLuaTask, ChangeEnviroWeather, ChangeFollowDampTime,
ChangeGadgetUIInteractHint, ChangePlayMode, ChangeTag, ChangeUGCRayTag,
ClearEndura, ClearGlobalPos, ClearGlobalValue, ClearLocalGadgets,
ClearLockTarget, ClearPos, ConfigAbilityAction, ControlEmotion,
CopyGlobalValue, CreateGadget, CreateMovingPlatform, CreateTile,
DamageByAttackValue, DebugLog, DestroyTile, DoBlink,
DoTileAction, DoWatcherSystemAction, DoWidgetSystemAction, DropSubfield,
DummyAction, DungeonFogEffects, ElementAttachForActivityGacha, EnableAIStealthy,
EnableAfterImage, EnableAvatarFlyStateTrail, EnableAvatarMoveOnWater, EnableBulletCollisionPluginTrigger,
EnableGadgetIntee, EnableHeadControl, EnableHitBoxByName, EnableMainInterface,
EnablePartControl, EnablePositionSynchronization, EnablePushColliderName, EnableRocketJump,
EnableSceneTransformByName, EnterCameraLock, EntityDoSkill, EquipAffixStart,
ExecuteGadgetLua, FireAISoundEvent, FireChargeBarEffect, FireEffect,
FireEffectFirework, FireEffectForStorm, FireFishingEvent, FireHitEffect,
FireSubEmitterEffect, FireUIEffect, FixedMonsterRushMove, ForceAirStateFly,
ForceEnableShakeOffButton, GenerateElemBall, GetFightProperty, GetInteractIdToGlobalValue,
GetPos, HealHP, HideUIBillBoard, IgnoreMoveColToRockCol,
KillGadget, KillPlayEntity, KillSelf, KillServerGadget,
LoseHP, ModifyAvatarSkillCD, ModifyVehicleSkillCD, PlayEmoSync,
Predicated, PushDvalinS01Process, PushInterActionByConfigPath, PushPos,
Randomed, ReTriggerAISkillInitialCD, RefreshUICombatBarLayout, RegisterAIActionPoint,
ReleaseAIActionPoint, RemoveAvatarSkillInfo, RemoveModifier, RemoveModifierByAbilityStateResistanceID,
RemoveServerBuff, RemoveUniqueModifier, RemoveVelocityForce, Repeated,
ResetAIAttackTarget, ResetAIResistTauntLevel, ResetAIThreatBroadcastRange, ResetAnimatorTrigger,
ReviveDeadAvatar, ReviveElemEnergy, ReviveStamina, SectorCityManeuver,
SendEffectTrigger, SendEffectTriggerToLineEffect, SendEvtElectricCoreMoveEnterP1, SendEvtElectricCoreMoveInterrupt,
ServerLuaCall, ServerLuaTriggerEvent, ServerMonsterLog, SetAIHitFeeling,
SetAISkillCDAvailableNow, SetAISkillCDMultiplier, SetAISkillGCD, SetAnimatorBool,
SetAnimatorFloat, SetAnimatorInt, SetAnimatorTrigger, SetAvatarCanShakeOff,
SetAvatarHitBuckets, SetCanDieImmediately, SetChargeBarValue, SetDvalinS01FlyState,
SetEmissionScaler, SetEntityScale, SetExtraAbilityEnable, SetExtraAbilityState,
SetGlobalDir, SetGlobalPos, SetGlobalValue, SetGlobalValueByTargetDistance,
SetGlobalValueToOverrideMap, SetKeepInAirVelocityForce, SetMaterialParamFloatByTransform, SetNeuronEnable,
SetOverrideMapValue, SetPartControlTarget, SetPoseBool, SetPoseFloat,
SetPoseInt, SetRandomOverrideMapValue, SetRegionalPlayVarValue, SetSelfAttackTarget,
SetSkillAnchor, SetSpecialCamera, SetSurroundAnchor, SetSystemValueToOverrideMap,
SetTargetNumToGlobalValue, SetUICombatBarAsh, SetUICombatBarSpark, SetVelocityIgnoreAirGY,
SetWeaponAttachPointRealName, SetWeaponBindState, ShowExtraAbility, ShowProgressBarAction,
ShowReminder, ShowScreenEffect, ShowTextMap, ShowUICombatBar,
StartDither, SumTargetWeightToSelfGlobalValue, Summon, SyncToStageScript,
TriggerAbility, TriggerAttackEvent, TriggerAttackTargetMapEvent, TriggerAudio,
TriggerAuxWeaponTrans, TriggerBullet, TriggerCreateGadgetToEquipPart, TriggerDropEquipParts,
TriggerFaceAnimation, TriggerGadgetInteractive, TriggerHideWeapon, TriggerSetCastShadow,
TriggerSetPassThrough, TriggerSetRenderersEnable, TriggerSetShadowRamp, TriggerSetVisible,
TriggerTaunt, TriggerThrowEquipPart, TriggerUGCGadgetMove, TryFindBlinkPoint,
TryFindBlinkPointByBorn, TryTriggerPlatformStartMove, TurnDirection, TurnDirectionToPos,
UpdateReactionDamage, UseSkillEliteSet, WidgetSkillStart;
}
@SerializedName("$type")
public Type type;
public String target;
@SerializedName(value = "amount", alternate = "PDLLIFICICJ")
public DynamicFloat amount = DynamicFloat.ZERO;
public DynamicFloat amountByCasterAttackRatio = DynamicFloat.ZERO;
public DynamicFloat amountByCasterCurrentHPRatio = DynamicFloat.ZERO;
public DynamicFloat amountByCasterMaxHPRatio = DynamicFloat.ZERO;
public DynamicFloat amountByGetDamage = DynamicFloat.ZERO;
public DynamicFloat amountByTargetCurrentHPRatio = DynamicFloat.ZERO;
public DynamicFloat amountByTargetMaxHPRatio = DynamicFloat.ZERO;
@SerializedName(value = "ignoreAbilityProperty", alternate = "HHFGADCJJDI")
public boolean ignoreAbilityProperty;
public String modifierName;
}
public static class AbilityConfigData {
public AbilityData Default;
}
public static class AbilityData {
public String abilityName;
@SerializedName(value="modifiers", alternate={"HNEIEGHMLKH"})
public Map<String, AbilityModifier> modifiers;
}
public static class AbilityModifierAction {
public String $type;
public AbilityModifierActionType type;
public String target;
public AbilityModifierValue amount;
public AbilityModifierValue amountByTargetCurrentHPRatio;
}
public static class AbilityModifierValue {
public boolean isFormula;
public boolean isDynamic;
public String dynamicKey;
}
public enum AbilityModifierActionType {
HealHP, ApplyModifier, LoseHP;
}
//The following should be implemented into DynamicFloat if older resource formats need to be supported
// public static class AbilityModifierValue {
// public boolean isFormula;
// public boolean isDynamic;
// public String dynamicKey;
// }
}
......@@ -8,6 +8,8 @@ import it.unimi.dsi.fastutil.objects.Object2FloatMap;
import lombok.val;
public class DynamicFloat {
public static DynamicFloat ZERO = new DynamicFloat(0f);
public static class StackOp {
enum Op {CONSTANT, KEY, ADD, SUB, MUL, DIV};
public Op op;
......
......@@ -5,7 +5,6 @@ import com.google.gson.annotations.SerializedName;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.DailyDungeonData;
import emu.grasscutter.utils.JsonUtils;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
......
......@@ -13,6 +13,8 @@ public class BuffData extends GameResource {
private float time;
private boolean isPersistent;
private ServerBuffType serverBuffType;
private String abilityName;
private String modifierName;
@Override
public int getId() {
......
......@@ -9,7 +9,9 @@ import emu.grasscutter.data.ResourceType;
import emu.grasscutter.data.common.ItemUseData;
import emu.grasscutter.game.inventory.*;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.game.props.ItemUseTarget;
import emu.grasscutter.game.props.ItemUseAction.ItemUseAction;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import lombok.Getter;
......@@ -46,8 +48,10 @@ public class ItemData extends GameResource {
private int[] satiationParams;
// Usable item
private ItemUseTarget useTarget;
private ItemUseTarget useTarget = ItemUseTarget.ITEM_USE_TARGET_NONE;
private List<ItemUseData> itemUse;
private List<ItemUseAction> itemUseActions;
private boolean useOnGain = false;
// Relic
private int mainPropDepotId;
......@@ -124,8 +128,16 @@ public class ItemData extends GameResource {
// Prevent material type from being null
this.materialType = this.materialType == null ? MaterialType.MATERIAL_NONE : this.materialType;
if (this.itemUse != null && !this.itemUse.isEmpty()) {
this.itemUseActions = this.itemUse.stream()
.filter(x -> x.getUseOp() != ItemUseOp.ITEM_USE_NONE)
.map(ItemUseAction::fromItemUseData)
.toList();
}
}
@Getter
public static class WeaponProperty {
private FightProperty propType;
......
......@@ -195,23 +195,23 @@ public final class AbilityManager extends BasePlayerManager {
private void invokeAction(AbilityModifierAction action, GameEntity target, GameEntity sourceEntity) {
switch (action.type) {
case HealHP -> {
}
case HealHP -> {}
case LoseHP -> {
if (action.amountByTargetCurrentHPRatio == null) {
return;
}
float damageAmount = 0;
float damageAmount = action.amount.get();
if (action.amount.isDynamic && action.amount.dynamicKey != null) {
damageAmount = sourceEntity.getMetaOverrideMap().getOrDefault(action.amount.dynamicKey, 0f);
}
// if (action.amount.isDynamic && action.amount.dynamicKey != null) {
// damageAmount = sourceEntity.getMetaOverrideMap().getOrDefault(action.amount.dynamicKey, 0f);
// }
if (damageAmount > 0) {
target.damage(damageAmount);
}
}
default -> {}
}
}
}
......
......@@ -206,6 +206,12 @@ public class Avatar {
return 0;
}
public boolean addSatiation(float value) {
if (this.satiation >= 100) return false;
this.satiation += value;
return true;
}
public GameItem getEquipBySlot(EquipType slot) {
return this.getEquips().get(slot.getValue());
}
......@@ -927,7 +933,7 @@ public class Avatar {
showAvatarInfo.putPropMap(PlayerProperty.PROP_EXP.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_EXP, this.getExp()));
showAvatarInfo.putPropMap(PlayerProperty.PROP_BREAK_LEVEL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_BREAK_LEVEL, this.getPromoteLevel()));
showAvatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_VAL.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_VAL, this.getSatiation()));
showAvatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_VAL, this.getSatiationPenalty()));
showAvatarInfo.putPropMap(PlayerProperty.PROP_SATIATION_PENALTY_TIME.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_SATIATION_PENALTY_TIME, this.getSatiationPenalty()));
int maxStamina = this.getPlayer().getProperty(PlayerProperty.PROP_MAX_STAMINA);
showAvatarInfo.putPropMap(PlayerProperty.PROP_MAX_STAMINA.getId(), ProtoHelper.newPropValue(PlayerProperty.PROP_MAX_STAMINA, maxStamina));
......
......@@ -8,7 +8,6 @@ import emu.grasscutter.data.excels.CombineData;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.net.proto.RetcodeOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.game.BaseGameSystem;
......@@ -43,24 +42,12 @@ public class CombineManger extends BaseGameSystem {
}
}
public boolean unlockCombineDiagram(Player player, GameItem diagramItem) {
// Make sure this is actually a diagram.
if (diagramItem.getItemData().getItemUse().get(0).getUseOp() != ItemUseOp.ITEM_USE_UNLOCK_COMBINE) {
return false;
public boolean unlockCombineDiagram(Player player, int combineId) {
if (!player.getUnlockedCombines().add(combineId)) {
return false; // Already unlocked
}
// Determine the combine item we should unlock.
int combineId = Integer.parseInt(diagramItem.getItemData().getItemUse().get(0).getUseParam()[0]);
// Remove the diagram from the player's inventory.
// We need to do this here, before sending CombineFormulaDataNotify, or the the combine UI won't correctly
// update when unlocking the diagram.
player.getInventory().removeItem(diagramItem, 1);
// Tell the client that this diagram is now unlocked and add the unlocked item to the player.
player.getUnlockedCombines().add(combineId);
player.sendPacket(new PacketCombineFormulaDataNotify(combineId));
return true;
}
......
......@@ -15,14 +15,13 @@ import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.gacha.GachaBanner.BannerType;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.inventory.ItemType;
import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.systems.InventorySystem;
import emu.grasscutter.net.proto.GachaItemOuterClass.GachaItem;
import emu.grasscutter.net.proto.GachaTransferItemOuterClass.GachaTransferItem;
import emu.grasscutter.net.proto.GetGachaInfoRspOuterClass.GetGachaInfoRsp;
......@@ -32,7 +31,6 @@ import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.game.GameServerTickEvent;
import emu.grasscutter.server.packet.send.PacketDoGachaRsp;
import emu.grasscutter.server.packet.send.PacketGachaWishRsp;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
......@@ -121,26 +119,10 @@ public class GachaSystem extends BaseGameSystem {
}
}
private synchronized int checkPlayerAvatarConstellationLevel(Player player, int itemId) { // Maybe this would be useful in the Player class?
ItemData itemData = GameData.getItemDataMap().get(itemId);
if ((itemData == null) || (itemData.getMaterialType() != MaterialType.MATERIAL_AVATAR)) {
return -2; // Not an Avatar
}
Avatar avatar = player.getAvatars().getAvatarById((itemId % 1000) + 10000000);
if (avatar == null) {
return -1; // Doesn't have
}
// Constellation
int constLevel = avatar.getCoreProudSkillLevel();
GameItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId + 100);
constLevel += (constItem == null)? 0 : constItem.getCount();
return constLevel;
}
private synchronized int[] removeC6FromPool(int[] itemPool, Player player) {
IntList temp = new IntArrayList();
for (int itemId : itemPool) {
if (checkPlayerAvatarConstellationLevel(player, itemId) < 6) {
if (InventorySystem.checkPlayerAvatarConstellationLevel(player, itemId) < 6) {
temp.add(itemId);
}
}
......@@ -314,7 +296,7 @@ public class GachaSystem extends BaseGameSystem {
boolean isTransferItem = false;
// Const check
int constellation = checkPlayerAvatarConstellationLevel(player, itemId);
int constellation = InventorySystem.checkPlayerAvatarConstellationLevel(player, itemId);
switch (constellation) {
case -2: // Is weapon
switch (itemData.getRankLevel()) {
......
......@@ -4,6 +4,7 @@ import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.IndexOptions;
import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.Transient;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.HomeWorldLevelData;
......@@ -25,12 +26,12 @@ import java.util.concurrent.ConcurrentHashMap;
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class GameHome {
@Id
String id;
@Indexed(options = @IndexOptions(unique = true))
long ownerUid;
@Transient Player player;
int level;
int exp;
......@@ -70,6 +71,8 @@ public class GameHome {
}
public void onOwnerLogin(Player player) {
if (this.player == null)
this.player = player;
player.getSession().send(new PacketHomeBasicInfoNotify(player, false));
player.getSession().send(new PacketPlayerHomeCompInfoNotify(player));
player.getSession().send(new PacketHomeComfortInfoNotify(player));
......@@ -78,30 +81,40 @@ public class GameHome {
player.getSession().send(new PacketUnlockedHomeBgmNotify(player));
}
public Player getPlayer() {
if (this.player == null)
this.player = Grasscutter.getGameServer().getPlayerByUid((int) this.ownerUid, true);
return this.player;
}
public HomeWorldLevelData getLevelData(){
return GameData.getHomeWorldLevelDataMap().get(level);
}
public void addUnlockedHomeBgm(int homeBgmId) {
getUnlockedHomeBgmList().add(homeBgmId);
public boolean addUnlockedHomeBgm(int homeBgmId) {
if (getUnlockedHomeBgmList().add(homeBgmId)) return false;
var player = this.getPlayer();
player.sendPacket(new PacketUnlockHomeBgmNotify(homeBgmId));
player.sendPacket(new PacketUnlockedHomeBgmNotify(player));
save();
return true;
}
public Set<Integer> getUnlockedHomeBgmListInfo() {
var list = getUnlockedHomeBgmList();
if (list == null) {
list = new HashSet<>();
addAllDefaultUnlockedBgmIds(list);
setUnlockedHomeBgmList(list);
if (this.unlockedHomeBgmList == null) {
this.unlockedHomeBgmList = new HashSet<>();
addAllDefaultUnlockedBgmIds(this.unlockedHomeBgmList);
save();
}
return list;
return this.unlockedHomeBgmList;
}
private void addAllDefaultUnlockedBgmIds(Set<Integer> list) {
GameData.getHomeWorldBgmDataMap().int2ObjectEntrySet().stream()
.filter(entry -> entry.getValue().isDefaultUnlock())
.forEach(entry -> list.add(entry.getIntKey()));
GameData.getHomeWorldBgmDataMap().forEach((id, data) -> {
if (data.isDefaultUnlock())
list.add(id);
});
}
}
......@@ -15,6 +15,7 @@ import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameDepot;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.ReliquaryAffixData;
import emu.grasscutter.data.excels.ReliquaryMainPropData;
......@@ -75,6 +76,10 @@ public class GameItem {
public GameItem(int itemId, int count) {
this(GameData.getItemDataMap().get(itemId), count);
}
public GameItem(ItemParamData itemParamData) {
this(itemParamData.getId(), itemParamData.getCount());
}
public GameItem(ItemData data) {
this(data, 1);
......
......@@ -22,6 +22,7 @@ import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.props.ItemUseAction.UseItemParams;
import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam;
import emu.grasscutter.server.packet.send.PacketAvatarEquipChangeNotify;
import emu.grasscutter.server.packet.send.PacketItemAddHintNotify;
......@@ -174,7 +175,12 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
private synchronized GameItem putItem(GameItem item) {
// Dont add items that dont have a valid item definition.
if (item.getItemData() == null) {
var data = item.getItemData();
if (data == null) return null;
if (data.isUseOnGain()) {
var params = new UseItemParams(this.player, data.getUseTarget());
this.player.getServer().getInventorySystem().useItemDirect(data, params);
return null;
}
......@@ -202,9 +208,6 @@ public class Inventory extends BasePlayerManager implements Iterable<GameItem> {
return item;
default:
switch (item.getItemData().getMaterialType()) {
case MATERIAL_ADSORBATE:
this.player.getEnergyManager().handlePickupElemBall(item);
return null;
case MATERIAL_AVATAR:
// Get avatar id
int avatarId = (item.getItemId() % 1000) + 10000000;
......
package emu.grasscutter.game.managers;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
......@@ -46,22 +45,13 @@ public class CookingManager extends BasePlayerManager {
/********************
* Unlocking for recipies.
********************/
public synchronized boolean unlockRecipe(GameItem recipeItem) {
// Make sure this is actually a cooking recipe.
if (recipeItem.getItemData().getItemUse().get(0).getUseOp() != ItemUseOp.ITEM_USE_UNLOCK_COOK_RECIPE) {
return false;
public boolean unlockRecipe(int id) {
if (this.player.getUnlockedRecipies().containsKey(id)) {
return false; // Recipe already unlocked
}
// Determine the recipe we should unlock.
int recipeId = Integer.parseInt(recipeItem.getItemData().getItemUse().get(0).getUseParam()[0]);
// Remove the item from the player's inventory.
// We need to do this here, before sending CookRecipeDataNotify, or the the UI won't correctly update.
player.getInventory().removeItem(recipeItem, 1);
// Tell the client that this blueprint is now unlocked and add the unlocked item to the player.
this.player.getUnlockedRecipies().put(recipeId, 0);
this.player.sendPacket(new PacketCookRecipeDataNotify(recipeId));
this.player.getUnlockedRecipies().put(id, 0);
this.player.sendPacket(new PacketCookRecipeDataNotify(id));
return true;
}
......
package emu.grasscutter.game.managers;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.game.home.FurnitureMakeSlotItem;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.net.proto.ItemParamOuterClass;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.packet.send.*;
......@@ -34,26 +31,19 @@ public class FurnitureManager extends BasePlayerManager {
player.getSession().send(new PacketUnlockedFurnitureSuiteDataNotify(player.getUnlockedFurnitureSuite()));
}
public synchronized boolean unlockFurnitureOrSuite(GameItem useItem) {
ItemUseOp itemUseOp = useItem.getItemData().getItemUse().get(0).getUseOp();
// Check
if (itemUseOp != ItemUseOp.ITEM_USE_UNLOCK_FURNITURE_SUITE && itemUseOp != ItemUseOp.ITEM_USE_UNLOCK_FURNITURE_FORMULA) {
return false;
public boolean unlockFurnitureFormula(int id) {
if (!player.getUnlockedFurniture().add(id)) {
return false; // Already unlocked!
}
notifyUnlockFurniture();
return true;
}
int furnitureIdOrSuiteId = Integer.parseInt(useItem.getItemData().getItemUse().get(0).getUseParam()[0]);
// Remove first
player.getInventory().removeItem(useItem, 1);
if (useItem.getItemData().getItemUse().get(0).getUseOp() == ItemUseOp.ITEM_USE_UNLOCK_FURNITURE_FORMULA) {
player.getUnlockedFurniture().add(furnitureIdOrSuiteId);
notifyUnlockFurniture();
}else {
player.getUnlockedFurnitureSuite().add(furnitureIdOrSuiteId);
notifyUnlockFurnitureSuite();
public boolean unlockFurnitureSuite(int id) {
if (!player.getUnlockedFurnitureSuite().add(id)) {
return false; // Already unlocked!
}
notifyUnlockFurnitureSuite();
return true;
}
......
......@@ -13,12 +13,17 @@ import emu.grasscutter.game.entity.EntityItem;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.MaterialType;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.game.props.ItemUseTarget;
import emu.grasscutter.game.props.MonsterType;
import emu.grasscutter.game.props.WeaponType;
import emu.grasscutter.game.props.ItemUseAction.ItemUseAction;
import emu.grasscutter.game.props.ItemUseAction.ItemUseAddEnergy;
import emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.AbilityActionGenerateElemBall;
import emu.grasscutter.net.proto.AbilityIdentifierOuterClass.AbilityIdentifier;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
......@@ -30,10 +35,10 @@ import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
......@@ -42,7 +47,7 @@ import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
import com.google.protobuf.InvalidProtocolBufferException;
public class EnergyManager extends BasePlayerManager {
private final Map<EntityAvatar, Integer> avatarNormalProbabilities;
private final Object2IntMap<EntityAvatar> avatarNormalProbabilities;
private boolean energyUsage; // Should energy usage be enabled for this player?
private final static Int2ObjectMap<List<EnergyDropInfo>> energyDropData = new Int2ObjectOpenHashMap<>();
......@@ -50,7 +55,7 @@ public class EnergyManager extends BasePlayerManager {
public EnergyManager(Player player) {
super(player);
this.avatarNormalProbabilities = new HashMap<>();
this.avatarNormalProbabilities = new Object2IntOpenHashMap<>();
this.energyUsage=GAME_OPTIONS.energyUsage;
}
......@@ -170,66 +175,9 @@ public class EnergyManager extends BasePlayerManager {
}
// Generate the particles.
var pos = new Position(action.getPos());
for (int i = 0; i < amount; i++) {
this.generateElemBall(itemId, new Position(action.getPos()), 1);
}
}
/**
* Pickup of elemental particles and orbs.
* @param elemBall The elemental particle or orb.
*/
public void handlePickupElemBall(GameItem elemBall) {
// Check if the item is indeed an energy particle/orb.
if (elemBall.getItemId() < 2001 ||elemBall.getItemId() > 2024) {
return;
}
// Determine the base amount of energy given by the particle/orb.
// Particles have a base amount of 1.0, and orbs a base amount of 3.0.
float baseEnergy = (elemBall.getItemId() <= 2008) ? 3.0f : 1.0f;
// Add energy to every team member.
for (int i = 0; i < this.player.getTeamManager().getActiveTeam().size(); i++) {
EntityAvatar entity = this.player.getTeamManager().getActiveTeam().get(i);
// On-field vs off-field multiplier.
// The on-field character gets no penalty.
// Off-field characters get a penalty depending on the team size, as follows:
// - 2 character team: 0.8
// - 3 character team: 0.7
// - 4 character team: 0.6
// - etc.
// We set a lower bound of 0.1 here, to avoid gaining no or negative energy.
float offFieldPenalty =
(this.player.getTeamManager().getCurrentCharacterIndex() == i)
? 1.0f
: 1.0f - this.player.getTeamManager().getActiveTeam().size() * 0.1f;
offFieldPenalty = Math.max(offFieldPenalty, 0.1f);
// Same element/neutral bonus.
// Same-element characters get a bonus of *3, while different-element characters get no bonus at all.
// For neutral particles/orbs, the multiplier is always *2.
if (entity.getAvatar().getSkillDepot() == null) {
continue;
}
ElementType avatarElement = entity.getAvatar().getSkillDepot().getElementType();
ElementType ballElement = switch (elemBall.getItemId()) {
case 2001, 2017 -> ElementType.Fire;
case 2002, 2018 -> ElementType.Water;
case 2003, 2019 -> ElementType.Grass;
case 2004, 2020 -> ElementType.Electric;
case 2005, 2021 -> ElementType.Wind;
case 2006, 2022 -> ElementType.Ice;
case 2007, 2023 -> ElementType.Rock;
default -> null;
};
float elementBonus = (ballElement == null) ? 2.0f : (avatarElement == ballElement) ? 3.0f : 1.0f;
// Add the energy.
entity.addEnergy(baseEnergy * elementBonus * offFieldPenalty * elemBall.getCount(), PropChangeReason.PROP_CHANGE_REASON_ENERGY_BALL);
this.generateElemBall(itemId, pos, 1);
}
}
......@@ -256,7 +204,7 @@ public class EnergyManager extends BasePlayerManager {
}
// Roll for energy.
int currentProbability = this.avatarNormalProbabilities.get(avatar);
int currentProbability = this.avatarNormalProbabilities.getInt(avatar);
int roll = ThreadLocalRandom.current().nextInt(0, 100);
// If the player wins the roll, we increase the avatar's energy and reset the probability.
......
......@@ -5,7 +5,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ForgeData;
......@@ -14,7 +13,6 @@ import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.ItemUseOp;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.net.proto.ForgeQueueDataOuterClass.ForgeQueueData;
import emu.grasscutter.net.proto.ForgeQueueManipulateReqOuterClass.ForgeQueueManipulateReq;
......@@ -38,24 +36,12 @@ public class ForgingManager extends BasePlayerManager {
/**********
Blueprint unlocking.
**********/
public synchronized boolean unlockForgingBlueprint(GameItem blueprintItem) {
// Make sure this is actually a forging blueprint.
if (blueprintItem.getItemData().getItemUse().get(0).getUseOp() != ItemUseOp.ITEM_USE_UNLOCK_FORGE) {
return false;
}
// Determine the forging item we should unlock.
int forgeId = Integer.parseInt(blueprintItem.getItemData().getItemUse().get(0).getUseParam()[0]);
// Remove the blueprint from the player's inventory.
// We need to do this here, before sending ForgeFormulaDataNotify, or the the forging UI won't correctly
// update when unlocking the blueprint.
player.getInventory().removeItem(blueprintItem, 1);
public boolean unlockForgingBlueprint(int id) {
// Tell the client that this blueprint is now unlocked and add the unlocked item to the player.
this.player.getUnlockedForgingBlueprints().add(forgeId);
this.player.sendPacket(new PacketForgeFormulaDataNotify(forgeId));
if (!this.player.getUnlockedForgingBlueprints().add(id)) {
return false; // Already unlocked
}
this.player.sendPacket(new PacketForgeFormulaDataNotify(id));
return true;
}
......
......@@ -29,49 +29,49 @@ import java.util.*;
public class StaminaManager extends BasePlayerManager {
// TODO: Skiff state detection?
private static final HashMap<String, HashSet<MotionState>> MotionStatesCategorized = new HashMap<>() {{
put("CLIMB", new HashSet<>(List.of(
private static final Map<String, Set<MotionState>> MotionStatesCategorized = new HashMap<>() {{
put("CLIMB", Set.of(
MotionState.MOTION_STATE_CLIMB, // sustained, when not moving no cost no recover
MotionState.MOTION_STATE_STANDBY_TO_CLIMB // NOT OBSERVED, see MOTION_JUMP_UP_WALL_FOR_STANDBY
)));
put("DASH", new HashSet<>(List.of(
));
put("DASH", Set.of(
MotionState.MOTION_STATE_DANGER_DASH, // sustained
MotionState.MOTION_STATE_DASH // sustained
)));
put("FLY", new HashSet<>(List.of(
));
put("FLY", Set.of(
MotionState.MOTION_STATE_FLY, // sustained
MotionState.MOTION_STATE_FLY_FAST, // sustained
MotionState.MOTION_STATE_FLY_SLOW, // sustained
MotionState.MOTION_STATE_POWERED_FLY // sustained, recover
)));
put("RUN", new HashSet<>(List.of(
));
put("RUN", Set.of(
MotionState.MOTION_STATE_DANGER_RUN, // sustained, recover
MotionState.MOTION_STATE_RUN // sustained, recover
)));
put("SKIFF", new HashSet<>(List.of(
));
put("SKIFF", Set.of(
MotionState.MOTION_STATE_SKIFF_BOARDING, // NOT OBSERVED even when boarding
MotionState.MOTION_STATE_SKIFF_DASH, // sustained, observed with waverider entity ID.
MotionState.MOTION_STATE_SKIFF_NORMAL, // sustained, OBSERVED when both normal and dashing
MotionState.MOTION_STATE_SKIFF_POWERED_DASH // sustained, recover
)));
put("STANDBY", new HashSet<>(List.of(
));
put("STANDBY", Set.of(
MotionState.MOTION_STATE_DANGER_STANDBY_MOVE, // sustained, recover
MotionState.MOTION_STATE_DANGER_STANDBY, // sustained, recover
MotionState.MOTION_STATE_LADDER_TO_STANDBY, // NOT OBSERVED
MotionState.MOTION_STATE_STANDBY_MOVE, // sustained, recover
MotionState.MOTION_STATE_STANDBY // sustained, recover
)));
put("SWIM", new HashSet<>(List.of(
));
put("SWIM", Set.of(
MotionState.MOTION_STATE_SWIM_IDLE, // sustained
MotionState.MOTION_STATE_SWIM_DASH, // immediate and sustained
MotionState.MOTION_STATE_SWIM_JUMP, // NOT OBSERVED
MotionState.MOTION_STATE_SWIM_MOVE // sustained
)));
put("WALK", new HashSet<>(List.of(
));
put("WALK", Set.of(
MotionState.MOTION_STATE_DANGER_WALK, // sustained, recover
MotionState.MOTION_STATE_WALK // sustained, recover
)));
put("OTHER", new HashSet<>(List.of(
));
put("OTHER", Set.of(
MotionState.MOTION_STATE_CLIMB_JUMP, // cost only once if repeated without switching state
MotionState.MOTION_STATE_DASH_BEFORE_SHAKE, // immediate one time sprint charge.
MotionState.MOTION_STATE_FIGHT, // immediate, if sustained then subsequent will be MOTION_NOTIFY
......@@ -79,13 +79,13 @@ public class StaminaManager extends BasePlayerManager {
MotionState.MOTION_STATE_NOTIFY, // can be either cost or recover - check previous state and check skill casting
MotionState.MOTION_STATE_SIT_IDLE, // sustained, recover
MotionState.MOTION_STATE_JUMP // recover
)));
put("NOCOST_NORECOVER", new HashSet<>(List.of(
));
put("NOCOST_NORECOVER", Set.of(
MotionState.MOTION_STATE_LADDER_SLIP, // NOT OBSERVED
MotionState.MOTION_STATE_SLIP, // sustained, no cost no recover
MotionState.MOTION_STATE_FLY_IDLE // NOT OBSERVED
)));
put("IGNORE", new HashSet<>(List.of(
));
put("IGNORE", Set.of(
// these states have no impact on stamina
MotionState.MOTION_STATE_CROUCH_IDLE,
MotionState.MOTION_STATE_CROUCH_MOVE,
......@@ -106,7 +106,7 @@ public class StaminaManager extends BasePlayerManager {
MotionState.MOTION_STATE_RESET,
MotionState.MOTION_STATE_STANDBY_TO_LADDER,
MotionState.MOTION_STATE_WATERFALL
)));
));
}};
private final Logger logger = Grasscutter.getLogger();
......@@ -127,9 +127,7 @@ public class StaminaManager extends BasePlayerManager {
private boolean lastSkillFirstTick = true;
private int vehicleId = -1;
private int vehicleStamina = GlobalVehicleMaxStamina;
private static final HashSet<Integer> TalentMovements = new HashSet<>(List.of(
10013, 10413
));
private static final Set<Integer> TalentMovements = Set.of(10013, 10413);
private static final HashMap<Integer, Float> ClimbFoodReductionMap = new HashMap<>() {{
// TODO: get real food id
put(0, 0.8f); // Sample food
......@@ -190,6 +188,17 @@ public class StaminaManager extends BasePlayerManager {
return vehicleStamina;
}
public boolean addCurrentStamina(int amount) {
var cur = this.getCurrentCharacterStamina();
var max = this.getMaxCharacterStamina();
if (cur >= max) return false;
var value = cur + amount;
if (value > max)
value = max;
this.player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, value);
return true;
}
public boolean registerBeforeUpdateStaminaListener(String listenerName, BeforeUpdateStaminaListener listener) {
if (beforeUpdateStaminaListeners.containsKey(listenerName)) {
return false;
......@@ -405,27 +414,17 @@ public class StaminaManager extends BasePlayerManager {
// Internal handler
private void handleImmediateStamina(GameSession session, @NotNull MotionState motionState) {
if (currentState == motionState) return;
switch (motionState) {
case MOTION_STATE_CLIMB:
if (currentState != MotionState.MOTION_STATE_CLIMB) {
case MOTION_STATE_CLIMB ->
updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START), true);
}
break;
case MOTION_STATE_DASH_BEFORE_SHAKE:
if (previousState != MotionState.MOTION_STATE_DASH_BEFORE_SHAKE) {
case MOTION_STATE_DASH_BEFORE_SHAKE ->
updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT), true);
}
break;
case MOTION_STATE_CLIMB_JUMP:
if (previousState != MotionState.MOTION_STATE_CLIMB_JUMP) {
case MOTION_STATE_CLIMB_JUMP ->
updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP), true);
}
break;
case MOTION_STATE_SWIM_DASH:
if (previousState != MotionState.MOTION_STATE_SWIM_DASH) {
case MOTION_STATE_SWIM_DASH ->
updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START), true);
}
break;
default -> {}
}
}
......@@ -526,20 +525,14 @@ public class StaminaManager extends BasePlayerManager {
// Bow avatar charged attack
Avatar currentAvatar = player.getTeamManager().getCurrentAvatarEntity().getAvatar();
switch (currentAvatar.getAvatarData().getWeaponType()) {
case WEAPON_BOW:
return getBowSustainedCost(skillCasting);
case WEAPON_CLAYMORE:
return getClaymoreSustainedCost(skillCasting);
case WEAPON_CATALYST:
return getCatalystCost(skillCasting);
case WEAPON_POLE:
return getPolearmCost(skillCasting);
case WEAPON_SWORD_ONE_HAND:
return getSwordCost(skillCasting);
}
return new Consumption();
return switch (currentAvatar.getAvatarData().getWeaponType()) {
case WEAPON_BOW -> getBowSustainedCost(skillCasting);
case WEAPON_CLAYMORE -> getClaymoreSustainedCost(skillCasting);
case WEAPON_CATALYST -> getCatalystCost(skillCasting);
case WEAPON_POLE -> getPolearmCost(skillCasting);
case WEAPON_SWORD_ONE_HAND -> getSwordCost(skillCasting);
default -> new Consumption();
};
}
private Consumption getClimbConsumption() {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment