From d1d39db56c17df737aa8f5c03d04bf69ea492902 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Fri, 14 Oct 2022 00:00:40 +1030 Subject: [PATCH] [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 --- .../java/emu/grasscutter/data/GameData.java | 3 + .../emu/grasscutter/data/ResourceLoader.java | 91 ++-- .../grasscutter/data/binout/AbilityData.java | 13 + .../data/binout/AbilityModifier.java | 122 +++-- .../grasscutter/data/common/DynamicFloat.java | 2 + .../grasscutter/data/common/PointData.java | 1 - .../emu/grasscutter/data/excels/BuffData.java | 2 + .../emu/grasscutter/data/excels/ItemData.java | 14 +- .../game/ability/AbilityManager.java | 12 +- .../emu/grasscutter/game/avatar/Avatar.java | 8 +- .../game/combine/CombineManger.java | 19 +- .../grasscutter/game/gacha/GachaSystem.java | 24 +- .../emu/grasscutter/game/home/GameHome.java | 37 +- .../grasscutter/game/inventory/GameItem.java | 5 + .../grasscutter/game/inventory/Inventory.java | 11 +- .../game/managers/CookingManager.java | 20 +- .../game/managers/FurnitureManager.java | 30 +- .../game/managers/energy/EnergyManager.java | 76 +-- .../game/managers/forging/ForgingManager.java | 24 +- .../game/managers/stamina/StaminaManager.java | 105 ++-- .../emu/grasscutter/game/player/Player.java | 4 +- .../game/player/PlayerBuffManager.java | 46 +- .../grasscutter/game/player/TeamManager.java | 5 +- .../ItemUseAction/ItemUseAcceptQuest.java | 19 + .../props/ItemUseAction/ItemUseAction.java | 78 +++ .../ItemUseAction/ItemUseAddAllEnergy.java | 23 + .../props/ItemUseAction/ItemUseAddCurHp.java | 22 + .../ItemUseAction/ItemUseAddCurStamina.java | 19 + .../ItemUseAction/ItemUseAddElemEnergy.java | 31 ++ .../props/ItemUseAction/ItemUseAddEnergy.java | 61 +++ .../props/ItemUseAction/ItemUseAddExp.java | 19 + .../props/ItemUseAction/ItemUseAddItem.java | 24 + .../ItemUseAction/ItemUseAddReliquaryExp.java | 19 + .../ItemUseAction/ItemUseAddSelectItem.java | 22 + .../ItemUseAction/ItemUseAddServerBuff.java | 24 + .../ItemUseAction/ItemUseAddWeaponExp.java | 19 + .../ItemUseAction/ItemUseChestSelectItem.java | 37 ++ .../ItemUseAction/ItemUseCombineItem.java | 29 ++ .../ItemUseAction/ItemUseGainAvatar.java | 43 ++ .../ItemUseAction/ItemUseGainCardProduct.java | 18 + .../ItemUseAction/ItemUseGainCostume.java | 20 + .../ItemUseAction/ItemUseGainFlycloak.java | 20 + .../ItemUseAction/ItemUseGainNameCard.java | 18 + .../ItemUseGrantSelectReward.java | 22 + .../game/props/ItemUseAction/ItemUseInt.java | 13 + .../ItemUseAction/ItemUseMakeGadget.java | 26 + .../ItemUseAction/ItemUseOpenRandomChest.java | 25 + .../ItemUseAction/ItemUseReliveAvatar.java | 18 + .../ItemUseAction/ItemUseSelectItems.java | 38 ++ .../ItemUseAction/ItemUseUnlockCodex.java | 19 + .../ItemUseAction/ItemUseUnlockCombine.java | 24 + .../ItemUseUnlockCookRecipe.java | 24 + .../ItemUseAction/ItemUseUnlockForge.java | 24 + .../ItemUseUnlockFurnitureFormula.java | 24 + .../ItemUseUnlockFurnitureSuite.java | 24 + .../ItemUseAction/ItemUseUnlockHomeBgm.java | 20 + .../ItemUseUnlockHomeModule.java | 19 + .../ItemUseUnlockPaidBattlePassNormal.java | 20 + .../props/ItemUseAction/UseItemParams.java | 29 ++ .../grasscutter/game/shop/ShopChestTable.java | 27 - .../emu/grasscutter/game/shop/ShopSystem.java | 33 +- .../game/systems/InventorySystem.java | 461 ++++++++---------- .../packet/recv/HandlerChangeHomeBgmReq.java | 6 +- .../server/packet/recv/HandlerUseItemReq.java | 2 +- .../resources/defaults/data/ShopChest.json | 153 ------ .../resources/defaults/data/ShopChest.v2.json | 29 ++ 66 files changed, 1533 insertions(+), 786 deletions(-) create mode 100644 src/main/java/emu/grasscutter/data/binout/AbilityData.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAcceptQuest.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAction.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddAllEnergy.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddCurHp.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddCurStamina.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddElemEnergy.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddEnergy.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddExp.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddItem.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddReliquaryExp.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddSelectItem.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddServerBuff.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddWeaponExp.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseChestSelectItem.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseCombineItem.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainAvatar.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainCardProduct.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainCostume.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainFlycloak.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainNameCard.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGrantSelectReward.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseInt.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseMakeGadget.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseOpenRandomChest.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseReliveAvatar.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseSelectItems.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockCodex.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockCombine.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockCookRecipe.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockForge.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockFurnitureFormula.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockFurnitureSuite.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockHomeBgm.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockHomeModule.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockPaidBattlePassNormal.java create mode 100644 src/main/java/emu/grasscutter/game/props/ItemUseAction/UseItemParams.java delete mode 100644 src/main/java/emu/grasscutter/game/shop/ShopChestTable.java delete mode 100644 src/main/resources/defaults/data/ShopChest.json create mode 100644 src/main/resources/defaults/data/ShopChest.v2.json diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 8ecc99bb3..ea9114572 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -25,10 +25,12 @@ public class GameData { // BinOutputs @Getter private static final Int2ObjectMap homeworldDefaultSaveData = new Int2ObjectOpenHashMap<>(); @Getter private static final Int2ObjectMap abilityHashes = new Int2ObjectOpenHashMap<>(); + @Deprecated(forRemoval = true) @Getter private static final Map abilityModifiers = new HashMap<>(); @Getter private static final Map gadgetConfigData = new HashMap<>(); @Getter private static final Map openConfigEntries = new HashMap<>(); @Deprecated(forRemoval = true) @Getter private static final Map scenePointEntries = new HashMap<>(); + protected static final Map abilityDataMap = new HashMap<>(); protected static final Int2ObjectMap scenePointEntryMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap mainQuestData = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap questsKeys = new Int2ObjectOpenHashMap<>(); @@ -136,6 +138,7 @@ public class GameData { public static Map 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);} diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index 1b6286f00..6fbd64697 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -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 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 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 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() { diff --git a/src/main/java/emu/grasscutter/data/binout/AbilityData.java b/src/main/java/emu/grasscutter/data/binout/AbilityData.java new file mode 100644 index 000000000..65ddb3abe --- /dev/null +++ b/src/main/java/emu/grasscutter/data/binout/AbilityData.java @@ -0,0 +1,13 @@ +package emu.grasscutter.data.binout; + +import java.util.Map; + +public class AbilityData { + public String abilityName; + public Map modifiers; + public boolean isDynamicAbility; + public Map abilitySpecials; + // abilityMixins + // onAbilityStart + // onKill +} diff --git a/src/main/java/emu/grasscutter/data/binout/AbilityModifier.java b/src/main/java/emu/grasscutter/data/binout/AbilityModifier.java index 147445b0a..68e9939b4 100644 --- a/src/main/java/emu/grasscutter/data/binout/AbilityModifier.java +++ b/src/main/java/emu/grasscutter/data/binout/AbilityModifier.java @@ -1,44 +1,98 @@ 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; + @SerializedName(value="onAdded", alternate={"KCICDEJLIJD"}) + public AbilityModifierAction[] onAdded; + @SerializedName(value="onThinkInterval", alternate={"PBDDACFFPOE"}) + public AbilityModifierAction[] onThinkInterval; + public AbilityModifierAction[] onRemoved; + public DynamicFloat duration = DynamicFloat.ZERO; - public static class AbilityConfigData { - public AbilityData Default; - } - - public static class AbilityData { - public String abilityName; - @SerializedName(value="modifiers", alternate={"HNEIEGHMLKH"}) - public Map 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; - } + 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; + } + + //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; + // } } diff --git a/src/main/java/emu/grasscutter/data/common/DynamicFloat.java b/src/main/java/emu/grasscutter/data/common/DynamicFloat.java index 65c1be88f..7dfd6a83e 100644 --- a/src/main/java/emu/grasscutter/data/common/DynamicFloat.java +++ b/src/main/java/emu/grasscutter/data/common/DynamicFloat.java @@ -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; diff --git a/src/main/java/emu/grasscutter/data/common/PointData.java b/src/main/java/emu/grasscutter/data/common/PointData.java index 9eebdb669..b2e9a2000 100644 --- a/src/main/java/emu/grasscutter/data/common/PointData.java +++ b/src/main/java/emu/grasscutter/data/common/PointData.java @@ -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; diff --git a/src/main/java/emu/grasscutter/data/excels/BuffData.java b/src/main/java/emu/grasscutter/data/excels/BuffData.java index 4219e9190..b908fc666 100644 --- a/src/main/java/emu/grasscutter/data/excels/BuffData.java +++ b/src/main/java/emu/grasscutter/data/excels/BuffData.java @@ -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() { diff --git a/src/main/java/emu/grasscutter/data/excels/ItemData.java b/src/main/java/emu/grasscutter/data/excels/ItemData.java index f4784fb43..3fe1a57b3 100644 --- a/src/main/java/emu/grasscutter/data/excels/ItemData.java +++ b/src/main/java/emu/grasscutter/data/excels/ItemData.java @@ -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 itemUse; + private List 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; diff --git a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java index 96518591d..7ca9334a3 100644 --- a/src/main/java/emu/grasscutter/game/ability/AbilityManager.java +++ b/src/main/java/emu/grasscutter/game/ability/AbilityManager.java @@ -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 -> {} } } } diff --git a/src/main/java/emu/grasscutter/game/avatar/Avatar.java b/src/main/java/emu/grasscutter/game/avatar/Avatar.java index 093990d5e..abdbf5c85 100644 --- a/src/main/java/emu/grasscutter/game/avatar/Avatar.java +++ b/src/main/java/emu/grasscutter/game/avatar/Avatar.java @@ -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)); diff --git a/src/main/java/emu/grasscutter/game/combine/CombineManger.java b/src/main/java/emu/grasscutter/game/combine/CombineManger.java index 295efeb88..e5213112b 100644 --- a/src/main/java/emu/grasscutter/game/combine/CombineManger.java +++ b/src/main/java/emu/grasscutter/game/combine/CombineManger.java @@ -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; } diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaSystem.java b/src/main/java/emu/grasscutter/game/gacha/GachaSystem.java index 6ffd6a9dc..692466965 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaSystem.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaSystem.java @@ -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()) { diff --git a/src/main/java/emu/grasscutter/game/home/GameHome.java b/src/main/java/emu/grasscutter/game/home/GameHome.java index 814d288e4..73ef64580 100644 --- a/src/main/java/emu/grasscutter/game/home/GameHome.java +++ b/src/main/java/emu/grasscutter/game/home/GameHome.java @@ -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 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 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); + }); } } diff --git a/src/main/java/emu/grasscutter/game/inventory/GameItem.java b/src/main/java/emu/grasscutter/game/inventory/GameItem.java index 6d5da5f34..93dd4622a 100644 --- a/src/main/java/emu/grasscutter/game/inventory/GameItem.java +++ b/src/main/java/emu/grasscutter/game/inventory/GameItem.java @@ -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); diff --git a/src/main/java/emu/grasscutter/game/inventory/Inventory.java b/src/main/java/emu/grasscutter/game/inventory/Inventory.java index e65b01fc3..0d12645a9 100644 --- a/src/main/java/emu/grasscutter/game/inventory/Inventory.java +++ b/src/main/java/emu/grasscutter/game/inventory/Inventory.java @@ -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 { 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 { 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; diff --git a/src/main/java/emu/grasscutter/game/managers/CookingManager.java b/src/main/java/emu/grasscutter/game/managers/CookingManager.java index 0aba25a8c..372bb4465 100644 --- a/src/main/java/emu/grasscutter/game/managers/CookingManager.java +++ b/src/main/java/emu/grasscutter/game/managers/CookingManager.java @@ -1,7 +1,6 @@ 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; } diff --git a/src/main/java/emu/grasscutter/game/managers/FurnitureManager.java b/src/main/java/emu/grasscutter/game/managers/FurnitureManager.java index d5b8a2dc4..87c2d2340 100644 --- a/src/main/java/emu/grasscutter/game/managers/FurnitureManager.java +++ b/src/main/java/emu/grasscutter/game/managers/FurnitureManager.java @@ -1,12 +1,9 @@ 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; } diff --git a/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java b/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java index 3fc42d730..e9eacd3d8 100644 --- a/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java +++ b/src/main/java/emu/grasscutter/game/managers/energy/EnergyManager.java @@ -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 avatarNormalProbabilities; + private final Object2IntMap avatarNormalProbabilities; private boolean energyUsage; // Should energy usage be enabled for this player? private final static Int2ObjectMap> 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. diff --git a/src/main/java/emu/grasscutter/game/managers/forging/ForgingManager.java b/src/main/java/emu/grasscutter/game/managers/forging/ForgingManager.java index 9fd8f82a5..347dce7bf 100644 --- a/src/main/java/emu/grasscutter/game/managers/forging/ForgingManager.java +++ b/src/main/java/emu/grasscutter/game/managers/forging/ForgingManager.java @@ -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; } diff --git a/src/main/java/emu/grasscutter/game/managers/stamina/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/stamina/StaminaManager.java index 17d521bb2..7450c571c 100644 --- a/src/main/java/emu/grasscutter/game/managers/stamina/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/stamina/StaminaManager.java @@ -29,49 +29,49 @@ import java.util.*; public class StaminaManager extends BasePlayerManager { // TODO: Skiff state detection? - private static final HashMap> MotionStatesCategorized = new HashMap<>() {{ - put("CLIMB", new HashSet<>(List.of( + private static final Map> 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 TalentMovements = new HashSet<>(List.of( - 10013, 10413 - )); + private static final Set TalentMovements = Set.of(10013, 10413); private static final HashMap 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() { diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 2bcb7820e..7261af269 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -653,7 +653,8 @@ public class Player { return (int) ((theLastDay.getTime() - now.getTime()) / (24 * 60 * 60 * 1000)); // By copilot } - public void rechargeMoonCard() { + public boolean rechargeMoonCard() { + if (this.moonCardDuration > 150) return false; // Can only stack up to 180 days inventory.addItem(new GameItem(203, 300)); if (!moonCard) { moonCard = true; @@ -666,6 +667,7 @@ public class Player { if (!moonCardGetTimes.contains(moonCardStartTime)) { moonCardGetTimes.add(moonCardStartTime); } + return true; } public void getTodayMoonCard() { diff --git a/src/main/java/emu/grasscutter/game/player/PlayerBuffManager.java b/src/main/java/emu/grasscutter/game/player/PlayerBuffManager.java index 8e4c95f74..00c4239f9 100644 --- a/src/main/java/emu/grasscutter/game/player/PlayerBuffManager.java +++ b/src/main/java/emu/grasscutter/game/player/PlayerBuffManager.java @@ -2,9 +2,12 @@ package emu.grasscutter.game.player; import java.util.LinkedList; import java.util.List; +import java.util.Optional; import emu.grasscutter.data.GameData; import emu.grasscutter.data.excels.BuffData; +import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.net.proto.ServerBuffChangeNotifyOuterClass.ServerBuffChangeNotify.ServerBuffChangeType; import emu.grasscutter.net.proto.ServerBuffOuterClass.ServerBuff; import emu.grasscutter.server.packet.send.PacketServerBuffChangeNotify; @@ -14,6 +17,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import lombok.AccessLevel; import lombok.Getter; +import lombok.val; @Getter(AccessLevel.PRIVATE) public class PlayerBuffManager extends BasePlayerManager { @@ -74,10 +78,50 @@ public class PlayerBuffManager extends BasePlayerManager { * @return True if a buff was added */ public synchronized boolean addBuff(int buffId, float duration) { + return addBuff(buffId, duration, null); + } + + /** + * Adds a server buff to the player. + * @param buffId Server buff id + * @param duration Duration of the buff in seconds. Set to 0 for an infinite buff. + * @param target Target avatar + * @return True if a buff was added + */ + public synchronized boolean addBuff(int buffId, float duration, Avatar target) { // Get buff excel data BuffData buffData = GameData.getBuffDataMap().get(buffId); if (buffData == null) return false; + boolean success = false; + + // Perform onAdded actions + success |= Optional.ofNullable(GameData.getAbilityData(buffData.getAbilityName())) + .map(data -> data.modifiers.get(buffData.getModifierName())) + .map(modifier -> modifier.onAdded) + .map(onAdded -> { + System.out.println("onAdded exists"); + boolean s = false; + for (var a: onAdded) { + switch (a.type) { + case HealHP: + System.out.println("Attempting heal"); + if (target == null) break; + float maxHp = target.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + float amount = a.amount.get() + a.amountByTargetMaxHPRatio.get() * maxHp; + target.getAsEntity().heal(amount); + s = true; + System.out.printf("Healed {}", amount); + break; + default: + break; + } + } + return s; + }) + .orElse(false); + System.out.println("Oh no"); + // Set duration if (duration < 0f) { duration = buffData.getTime(); @@ -85,7 +129,7 @@ public class PlayerBuffManager extends BasePlayerManager { // Dont add buff if duration is equal or less than 0 if (duration <= 0) { - return false; + return success; } // Clear previous buff if it exists diff --git a/src/main/java/emu/grasscutter/game/player/TeamManager.java b/src/main/java/emu/grasscutter/game/player/TeamManager.java index 2dc7091f2..aa68f6bf8 100644 --- a/src/main/java/emu/grasscutter/game/player/TeamManager.java +++ b/src/main/java/emu/grasscutter/game/player/TeamManager.java @@ -568,10 +568,7 @@ public class TeamManager extends BasePlayerDataManager { return false; } - entity.setFightProperty( - FightProperty.FIGHT_PROP_CUR_HP, - entity.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * .1f - ); + entity.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 1f); this.getPlayer().sendPacket(new PacketAvatarFightPropUpdateNotify(entity.getAvatar(), FightProperty.FIGHT_PROP_CUR_HP)); this.getPlayer().sendPacket(new PacketAvatarLifeStateChangeNotify(entity.getAvatar())); return true; diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAcceptQuest.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAcceptQuest.java new file mode 100644 index 000000000..cf0fb3be0 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAcceptQuest.java @@ -0,0 +1,19 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseAcceptQuest extends ItemUseInt { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_ACCEPT_QUEST; + } + + public ItemUseAcceptQuest(String[] useParam) { + super(useParam); + } + + @Override + public boolean useItem(UseItemParams params) { + return (params.player.getQuestManager().addQuest(this.i) != null); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAction.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAction.java new file mode 100644 index 000000000..63b053b25 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAction.java @@ -0,0 +1,78 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.data.common.ItemUseData; +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseAction { + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_NONE; + } + + public static ItemUseAction fromItemUseData(ItemUseData data) { + var useParam = data.getUseParam(); + return switch (data.getUseOp()) { + case ITEM_USE_NONE -> null; + // Uprade materials - no direct usage + case ITEM_USE_ADD_EXP -> new ItemUseAddExp(useParam); + case ITEM_USE_ADD_RELIQUARY_EXP -> new ItemUseAddReliquaryExp(useParam); + case ITEM_USE_ADD_WEAPON_EXP -> new ItemUseAddWeaponExp(useParam); + // Energy pickups + case ITEM_USE_ADD_ALL_ENERGY -> new ItemUseAddAllEnergy(useParam); + case ITEM_USE_ADD_ELEM_ENERGY -> new ItemUseAddElemEnergy(useParam); + // Give items + case ITEM_USE_ADD_ITEM -> new ItemUseAddItem(useParam); + case ITEM_USE_GAIN_AVATAR -> new ItemUseGainAvatar(useParam); + case ITEM_USE_GAIN_COSTUME -> new ItemUseGainCostume(useParam); // TODO - real success/fail + case ITEM_USE_GAIN_FLYCLOAK -> new ItemUseGainFlycloak(useParam); // TODO - real success/fail + case ITEM_USE_GAIN_NAME_CARD -> new ItemUseGainNameCard(useParam); // TODO + case ITEM_USE_CHEST_SELECT_ITEM -> new ItemUseChestSelectItem(useParam); + case ITEM_USE_ADD_SELECT_ITEM -> new ItemUseAddSelectItem(useParam); + case ITEM_USE_GRANT_SELECT_REWARD -> new ItemUseGrantSelectReward(useParam); + case ITEM_USE_COMBINE_ITEM -> new ItemUseCombineItem(useParam); + case ITEM_USE_OPEN_RANDOM_CHEST -> new ItemUseOpenRandomChest(useParam); + // Food effects + case ITEM_USE_RELIVE_AVATAR -> new ItemUseReliveAvatar(useParam); // First action for revival food. Should we worry about race conditions in parallel streams? + case ITEM_USE_ADD_CUR_HP -> new ItemUseAddCurHp(useParam); + case ITEM_USE_ADD_CUR_STAMINA -> new ItemUseAddCurStamina(useParam); + case ITEM_USE_ADD_SERVER_BUFF -> new ItemUseAddServerBuff(useParam); + case ITEM_USE_MAKE_GADGET -> new ItemUseMakeGadget(useParam); + // Unlock recipes - TODO: allow scheduling packets for after recipe is removed + case ITEM_USE_UNLOCK_COMBINE -> new ItemUseUnlockCombine(useParam); + case ITEM_USE_UNLOCK_CODEX -> new ItemUseUnlockCodex(useParam); // TODO: No backend for this yet + case ITEM_USE_UNLOCK_COOK_RECIPE -> new ItemUseUnlockCookRecipe(useParam); + case ITEM_USE_UNLOCK_FORGE -> new ItemUseUnlockForge(useParam); + case ITEM_USE_UNLOCK_FURNITURE_FORMULA -> new ItemUseUnlockFurnitureFormula(useParam); + case ITEM_USE_UNLOCK_FURNITURE_SUITE -> new ItemUseUnlockFurnitureSuite(useParam); + case ITEM_USE_UNLOCK_HOME_MODULE -> new ItemUseUnlockHomeModule(useParam); // No backend for this yet + case ITEM_USE_UNLOCK_HOME_BGM -> new ItemUseUnlockHomeBgm(useParam); + // Account things + case ITEM_USE_ACCEPT_QUEST -> new ItemUseAcceptQuest(useParam); + case ITEM_USE_GAIN_CARD_PRODUCT -> new ItemUseGainCardProduct(useParam); + case ITEM_USE_UNLOCK_PAID_BATTLE_PASS_NORMAL -> new ItemUseUnlockPaidBattlePassNormal(useParam); // TODO: add paid BP + + // Unused in current resources + case ITEM_USE_DEL_SERVER_BUFF -> null; + case ITEM_USE_ADD_BIG_TALENT_POINT -> null; + case ITEM_USE_GAIN_RESIN_CARD_PRODUCT -> null; + case ITEM_USE_TRIGGER_ABILITY -> null; + case ITEM_USE_ADD_TREASURE_MAP_BONUS_REGION_FRAGMENT -> null; + // Used in current resources but no point yet + case ITEM_USE_ADD_PERSIST_STAMINA -> null; // [int amount] one Test item + case ITEM_USE_ADD_TEMPORARY_STAMINA -> null; // [int amount] one Test item + case ITEM_USE_ADD_DUNGEON_COND_TIME -> null; // [int 1, int 15 or 20] - minigame shards + case ITEM_USE_ADD_CHANNELLER_SLAB_BUFF -> null; // [int] minigame buffs + case ITEM_USE_ADD_REGIONAL_PLAY_VAR -> null; // [String, int] - coral butterfly effect + }; + } + + public boolean useItem(UseItemParams params) { + // An item must return true on at least one of its actions to count as successfully used. + // If all of the actions return false, the item will not be consumed from inventory. + return false; + } + + public boolean postUseItem(UseItemParams params) { + // This is run after the item has been consumed from inventory. + return false; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddAllEnergy.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddAllEnergy.java new file mode 100644 index 000000000..73d84eed7 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddAllEnergy.java @@ -0,0 +1,23 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ElementType; +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseAddAllEnergy extends ItemUseAddEnergy { + private float energy = 0f; + + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_ADD_ALL_ENERGY; + } + + public ItemUseAddAllEnergy(String[] useParam) { + try { + this.energy = Float.parseFloat(useParam[0]); + } catch (Exception ignored) {} + } + + public float getAddEnergy(ElementType avatarElement) { + return this.energy; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddCurHp.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddCurHp.java new file mode 100644 index 000000000..821108283 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddCurHp.java @@ -0,0 +1,22 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseAddCurHp extends ItemUseInt { + private String icon; + + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_ADD_CUR_HP; + } + + public ItemUseAddCurHp(String[] useParam) { + super(useParam); + this.icon = useParam[1]; + } + + @Override + public boolean useItem(UseItemParams params) { + return (params.targetAvatar.getAsEntity().heal(params.count * this.i) > 0.01); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddCurStamina.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddCurStamina.java new file mode 100644 index 000000000..44b3d5c38 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddCurStamina.java @@ -0,0 +1,19 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseAddCurStamina extends ItemUseInt { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_ADD_CUR_STAMINA; + } + + public ItemUseAddCurStamina(String[] useParam) { + super(useParam); + } + + @Override + public boolean useItem(UseItemParams params) { + return params.player.getStaminaManager().addCurrentStamina(this.i); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddElemEnergy.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddElemEnergy.java new file mode 100644 index 000000000..be1e90245 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddElemEnergy.java @@ -0,0 +1,31 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ElementType; +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseAddElemEnergy extends ItemUseAddEnergy { + private ElementType element = ElementType.None; + private float elemEnergy = 0f; + private float otherEnergy = 0f; + + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_ADD_ELEM_ENERGY; + } + + public ItemUseAddElemEnergy(String[] useParam) { + try { + this.element = ElementType.getTypeByValue(Integer.parseInt(useParam[0])); + } catch (Exception ignored) {} + try { + this.elemEnergy = Float.parseFloat(useParam[1]); + } catch (Exception ignored) {} + try { + this.otherEnergy = Float.parseFloat(useParam[2]); + } catch (Exception ignored) {} + } + + public float getAddEnergy(ElementType avatarElement) { + return (avatarElement == this.element) ? this.elemEnergy : this.otherEnergy; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddEnergy.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddEnergy.java new file mode 100644 index 000000000..e02c6feb3 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddEnergy.java @@ -0,0 +1,61 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.data.excels.AvatarSkillDepotData; +import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.props.ElementType; +import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; + +public abstract class ItemUseAddEnergy extends ItemUseAction { + public abstract float getAddEnergy(ElementType avatarElement); + + public float getAddEnergy(AvatarSkillDepotData depot) { + if (depot == null) return 0f; + var element = depot.getElementType(); + if (element == null) return 0f; + return this.getAddEnergy(element); + } + + @Override + public boolean useItem(UseItemParams params) { + var teamManager = params.player.getTeamManager(); + return switch (params.itemUseTarget) { + case ITEM_USE_TARGET_CUR_AVATAR -> { + this.addEnergy(teamManager.getCurrentAvatarEntity().getAvatar(), params.count); + yield true; // Always consume elem balls + } + case ITEM_USE_TARGET_CUR_TEAM -> { + var activeTeam = teamManager.getActiveTeam(); + // On-field vs off-field multiplier. + // The on-field character gets full amount, off-field characters get less depending on the team size. + final float offFieldRatio = switch(activeTeam.size()) { + case 2 -> 0.8f; + case 3 -> 0.7f; + default -> 0.6f; + }; + final int currentCharacterIndex = teamManager.getCurrentCharacterIndex(); + + // Add energy to every team member. + for (int i = 0; i < activeTeam.size(); i++) { + var avatar = activeTeam.get(i).getAvatar(); + if (i == currentCharacterIndex) + this.addEnergy(avatar, params.count); + else + this.addEnergy(avatar, params.count * offFieldRatio); + } + + yield true; // Always consume elem balls + } + case ITEM_USE_TARGET_SPECIFY_AVATAR, ITEM_USE_TARGET_SPECIFY_ALIVE_AVATAR, ITEM_USE_TARGET_SPECIFY_DEAD_AVATAR -> + this.addEnergy(params.targetAvatar, params.count); // Targeted items might care about this + case ITEM_USE_TARGET_NONE -> false; + }; + } + + private boolean addEnergy(Avatar avatar, float multiplier) { + float energy = this.getAddEnergy(avatar.getSkillDepot()) * multiplier; + if (energy < 0.01f) + return false; + avatar.getAsEntity().addEnergy(energy, PropChangeReason.PROP_CHANGE_REASON_ENERGY_BALL); + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddExp.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddExp.java new file mode 100644 index 000000000..c140c8331 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddExp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; +import lombok.Getter; + +public class ItemUseAddExp extends ItemUseAction { + @Getter private int exp = 0; + + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_ADD_EXP; + } + + public ItemUseAddExp(String[] useParam) { + try { + this.exp = Integer.parseInt(useParam[0]); + } catch (NumberFormatException ignored) {} + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddItem.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddItem.java new file mode 100644 index 000000000..592c62776 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddItem.java @@ -0,0 +1,24 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseAddItem extends ItemUseInt { + private int count = 0; + + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_ADD_ITEM; + } + + public ItemUseAddItem(String[] useParam) { + super(useParam); + try { + this.count = Integer.parseInt(useParam[1]); + } catch (NumberFormatException ignored) {} + } + + @Override + public boolean useItem(UseItemParams params) { + return params.player.getInventory().addItem(this.i, this.count * params.count); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddReliquaryExp.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddReliquaryExp.java new file mode 100644 index 000000000..d71296b64 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddReliquaryExp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; +import lombok.Getter; + +public class ItemUseAddReliquaryExp extends ItemUseAction { + @Getter private int exp = 0; + + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_ADD_RELIQUARY_EXP; + } + + public ItemUseAddReliquaryExp(String[] useParam) { + try { + this.exp = Integer.parseInt(useParam[0]); + } catch (NumberFormatException ignored) {} + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddSelectItem.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddSelectItem.java new file mode 100644 index 000000000..a9bf45839 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddSelectItem.java @@ -0,0 +1,22 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseAddSelectItem extends ItemUseSelectItems { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_ADD_SELECT_ITEM; + } + + public ItemUseAddSelectItem(String[] useParam) { + String[] options = useParam[0].split(","); + this.optionItemIds = new int[options.length]; + for (int i = 0; i < options.length; i++) { + try { + this.optionItemIds[i] = Integer.parseInt(options[i]); + } catch (NumberFormatException ignored) { + this.optionItemIds[i] = INVALID; + } + }; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddServerBuff.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddServerBuff.java new file mode 100644 index 000000000..890b3afee --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddServerBuff.java @@ -0,0 +1,24 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseAddServerBuff extends ItemUseInt { + private int duration = 0; + + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_ADD_SERVER_BUFF; + } + + public ItemUseAddServerBuff(String[] useParam) { + super(useParam); + try { + this.duration = Integer.parseInt(useParam[1]); + } catch (NumberFormatException ignored) {} + } + + @Override + public boolean useItem(UseItemParams params) { + return params.player.getBuffManager().addBuff(this.i, this.duration, params.targetAvatar); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddWeaponExp.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddWeaponExp.java new file mode 100644 index 000000000..b883bd804 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseAddWeaponExp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; +import lombok.Getter; + +public class ItemUseAddWeaponExp extends ItemUseAction { + @Getter private int exp = 0; + + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_ADD_WEAPON_EXP; + } + + public ItemUseAddWeaponExp(String[] useParam) { + try { + this.exp = Integer.parseInt(useParam[0]); + } catch (NumberFormatException ignored) {} + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseChestSelectItem.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseChestSelectItem.java new file mode 100644 index 000000000..12fed0f64 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseChestSelectItem.java @@ -0,0 +1,37 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseChestSelectItem extends ItemUseSelectItems { + private int[] optionItemCounts; + + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_CHEST_SELECT_ITEM; + } + + public ItemUseChestSelectItem(String[] useParam) { + String[] options = useParam[0].split(","); + this.optionItemIds = new int[options.length]; + this.optionItemCounts = new int[options.length]; + for (int i = 0; i < options.length; i++) { + String[] optionParts = options[i].split(":"); + try { + this.optionItemIds[i] = Integer.parseInt(optionParts[0]); + } catch (NumberFormatException ignored) { + this.optionItemIds[i] = INVALID; + } + try { + this.optionItemCounts[i] = Integer.parseInt(optionParts[1]); + } catch (NumberFormatException ignored) { + this.optionItemCounts[i] = INVALID; + } + }; + } + + @Override + protected int getItemCount(int index) { + if ((optionItemCounts == null) || (index < 0) || (index > optionItemCounts.length)) return INVALID; + return this.optionItemCounts[index]; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseCombineItem.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseCombineItem.java new file mode 100644 index 000000000..2b4d34970 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseCombineItem.java @@ -0,0 +1,29 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseCombineItem extends ItemUseInt { + private int resultId = 0; + private int resultCount = 1; + + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_COMBINE_ITEM; + } + + public ItemUseCombineItem(String[] useParam) { + super(useParam); + try { + this.resultId = Integer.parseInt(useParam[1]); + } catch (NumberFormatException ignored) {} + try { + this.resultCount = Integer.parseInt(useParam[2]); + } catch (NumberFormatException ignored) {} + } + + @Override + public boolean useItem(UseItemParams params) { + if (params.count != this.i) return false; // Wrong amount of fragments supplied! + return params.player.getInventory().addItem(this.resultId, this.resultCount); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainAvatar.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainAvatar.java new file mode 100644 index 000000000..fecd509ab --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainAvatar.java @@ -0,0 +1,43 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.props.ItemUseOp; +import emu.grasscutter.game.systems.InventorySystem; + +public class ItemUseGainAvatar extends ItemUseInt { + private int level = 1; + private int constellation = 0; + + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_GAIN_AVATAR; + } + + public ItemUseGainAvatar(String[] useParam) { + super(useParam); + try { + this.level = Integer.parseInt(useParam[1]); + } catch (NumberFormatException ignored) {} + try { + this.constellation = Integer.parseInt(useParam[2]); + } catch (NumberFormatException ignored) {} + } + + @Override + public boolean useItem(UseItemParams params) { + int haveConstellation = InventorySystem.checkPlayerAvatarConstellationLevel(params.player, this.i); + if (haveConstellation == -2 || haveConstellation >= 6) { + return false; + } else if (haveConstellation == -1) { + var avatar = new Avatar(this.i); + avatar.setLevel(this.level); + avatar.forceConstellationLevel(this.constellation); + avatar.recalcStats(); + params.player.addAvatar(avatar); + return true; + } else { + int itemId = (this.i % 1000) + 100; + return params.player.getInventory().addItem(itemId); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainCardProduct.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainCardProduct.java new file mode 100644 index 000000000..96658e31d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainCardProduct.java @@ -0,0 +1,18 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseGainCardProduct extends ItemUseAction { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_GAIN_CARD_PRODUCT; + } + + public ItemUseGainCardProduct(String[] useParam) { + } + + @Override + public boolean useItem(UseItemParams params) { + return params.player.rechargeMoonCard(); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainCostume.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainCostume.java new file mode 100644 index 000000000..a712f60bb --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainCostume.java @@ -0,0 +1,20 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseGainCostume extends ItemUseInt { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_GAIN_COSTUME; + } + + public ItemUseGainCostume(String[] useParam) { + super(useParam); + } + + @Override + public boolean useItem(UseItemParams params) { + params.player.getInventory().addItem(this.i); // TODO: Currently this returns false for all virtual items - need to have a proper success/fail + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainFlycloak.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainFlycloak.java new file mode 100644 index 000000000..4077b6783 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainFlycloak.java @@ -0,0 +1,20 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseGainFlycloak extends ItemUseInt { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_GAIN_FLYCLOAK; + } + + public ItemUseGainFlycloak(String[] useParam) { + super(useParam); + } + + @Override + public boolean useItem(UseItemParams params) { + params.player.getInventory().addItem(this.i); // TODO: Currently this returns false for all virtual items - need to have a proper success/fail + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainNameCard.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainNameCard.java new file mode 100644 index 000000000..ac4695269 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGainNameCard.java @@ -0,0 +1,18 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseGainNameCard extends ItemUseAction { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_GAIN_NAME_CARD; + } + + public ItemUseGainNameCard(String[] useParam) { + } + + @Override + public boolean useItem(UseItemParams params) { + return false; // TODO: work out if this is actually used and how to get the namecard id + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGrantSelectReward.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGrantSelectReward.java new file mode 100644 index 000000000..68fcad2cb --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseGrantSelectReward.java @@ -0,0 +1,22 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseGrantSelectReward extends ItemUseSelectItems { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_ADD_SELECT_ITEM; + } + + public ItemUseGrantSelectReward(String[] useParam) { + String[] options = useParam[0].split(","); + this.optionItemIds = new int[options.length]; + for (int i = 0; i < options.length; i++) { + try { + this.optionItemIds[i] = Integer.parseInt(options[i]); + } catch (NumberFormatException ignored) { + this.optionItemIds[i] = INVALID; + } + }; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseInt.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseInt.java new file mode 100644 index 000000000..bce01a9d2 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseInt.java @@ -0,0 +1,13 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import lombok.Getter; + +public abstract class ItemUseInt extends ItemUseAction { + @Getter protected int i = 0; + + public ItemUseInt(String[] useParam) { + try { + this.i = Integer.parseInt(useParam[0]); + } catch (NumberFormatException ignored) {} + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseMakeGadget.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseMakeGadget.java new file mode 100644 index 000000000..d46ca8fb6 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseMakeGadget.java @@ -0,0 +1,26 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.entity.EntityVehicle; +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseMakeGadget extends ItemUseInt { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_MAKE_GADGET; + } + + public ItemUseMakeGadget(String[] useParam) { + super(useParam); + } + + @Override + public boolean useItem(UseItemParams params) { + var player = params.player; + var scene = player.getScene(); + var pos = player.getPosition().nearby2d(1f); + var rot = player.getRotation().clone(); + var e = new EntityVehicle(scene, player, this.i, 0, pos, rot); + scene.addEntity(e); + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseOpenRandomChest.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseOpenRandomChest.java new file mode 100644 index 000000000..6c391d0bb --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseOpenRandomChest.java @@ -0,0 +1,25 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseOpenRandomChest extends ItemUseInt { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_OPEN_RANDOM_CHEST; + } + + public ItemUseOpenRandomChest(String[] useParam) { + super(useParam); + } + + @Override + public boolean useItem(UseItemParams params) { // cash shop material bundles + var rewardItems = params.player.getServer().getShopSystem().getShopChestData(this.i).stream().map(GameItem::new).toList(); + if (!rewardItems.isEmpty()) { + params.player.getInventory().addItems(rewardItems, ActionReason.Shop); + } + return false; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseReliveAvatar.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseReliveAvatar.java new file mode 100644 index 000000000..06646e11a --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseReliveAvatar.java @@ -0,0 +1,18 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseReliveAvatar extends ItemUseAction { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_RELIVE_AVATAR; + } + + public ItemUseReliveAvatar(String[] useParam) { + } + + @Override + public boolean useItem(UseItemParams params) { + return params.player.getTeamManager().reviveAvatar(params.targetAvatar); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseSelectItems.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseSelectItems.java new file mode 100644 index 000000000..280709688 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseSelectItems.java @@ -0,0 +1,38 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.props.ActionReason; + +public abstract class ItemUseSelectItems extends ItemUseAction { + protected static final int INVALID = -1; + protected int[] optionItemIds; + + protected int getItemId(int index) { + if ((optionItemIds == null) || (index < 0) || (index > optionItemIds.length)) return INVALID; + return this.optionItemIds[index]; + } + + protected int getItemCount(int index) { + return 1; + } + + protected GameItem getItemStack(int index, int useCount) { + int id = this.getItemId(index); + int count = this.getItemCount(index); + if (id == INVALID || count == INVALID) return null; + + var item = GameData.getItemDataMap().get(id); + if (item == null) return null; + + return new GameItem(item, count * useCount); + } + + @Override + public boolean useItem(UseItemParams params) { + var itemStack = this.getItemStack(params.optionId - 1, params.count); + if (itemStack == null) return false; + + return params.player.getInventory().addItem(itemStack, ActionReason.Shop); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockCodex.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockCodex.java new file mode 100644 index 000000000..0d62e7709 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockCodex.java @@ -0,0 +1,19 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseUnlockCodex extends ItemUseInt { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_UNLOCK_CODEX; + } + + public ItemUseUnlockCodex(String[] useParam) { + super(useParam); + } + + @Override + public boolean useItem(UseItemParams params) { + return false; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockCombine.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockCombine.java new file mode 100644 index 000000000..54615e196 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockCombine.java @@ -0,0 +1,24 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseUnlockCombine extends ItemUseInt { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_UNLOCK_COMBINE; + } + + public ItemUseUnlockCombine(String[] useParam) { + super(useParam); + } + + @Override + public boolean useItem(UseItemParams params) { + return true; + } + + @Override + public boolean postUseItem(UseItemParams params) { + return params.player.getServer().getCombineSystem().unlockCombineDiagram(params.player, this.i); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockCookRecipe.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockCookRecipe.java new file mode 100644 index 000000000..bf20227f4 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockCookRecipe.java @@ -0,0 +1,24 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseUnlockCookRecipe extends ItemUseInt { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_UNLOCK_COOK_RECIPE; + } + + public ItemUseUnlockCookRecipe(String[] useParam) { + super(useParam); + } + + @Override + public boolean useItem(UseItemParams params) { + return true; + } + + @Override + public boolean postUseItem(UseItemParams params) { + return params.player.getCookingManager().unlockRecipe(this.i); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockForge.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockForge.java new file mode 100644 index 000000000..ffa4d5b4e --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockForge.java @@ -0,0 +1,24 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseUnlockForge extends ItemUseInt { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_UNLOCK_FORGE; + } + + public ItemUseUnlockForge(String[] useParam) { + super(useParam); + } + + @Override + public boolean useItem(UseItemParams params) { + return true; + } + + @Override + public boolean postUseItem(UseItemParams params) { + return params.player.getForgingManager().unlockForgingBlueprint(this.i); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockFurnitureFormula.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockFurnitureFormula.java new file mode 100644 index 000000000..d6d210237 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockFurnitureFormula.java @@ -0,0 +1,24 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseUnlockFurnitureFormula extends ItemUseInt { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_UNLOCK_FURNITURE_FORMULA; + } + + public ItemUseUnlockFurnitureFormula(String[] useParam) { + super(useParam); + } + + @Override + public boolean useItem(UseItemParams params) { + return true; + } + + @Override + public boolean postUseItem(UseItemParams params) { + return params.player.getFurnitureManager().unlockFurnitureFormula(this.i); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockFurnitureSuite.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockFurnitureSuite.java new file mode 100644 index 000000000..328e7a63a --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockFurnitureSuite.java @@ -0,0 +1,24 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseUnlockFurnitureSuite extends ItemUseInt { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_UNLOCK_FURNITURE_SUITE; + } + + public ItemUseUnlockFurnitureSuite(String[] useParam) { + super(useParam); + } + + @Override + public boolean useItem(UseItemParams params) { + return true; + } + + @Override + public boolean postUseItem(UseItemParams params) { + return params.player.getFurnitureManager().unlockFurnitureSuite(this.i); + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockHomeBgm.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockHomeBgm.java new file mode 100644 index 000000000..d80d44ca7 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockHomeBgm.java @@ -0,0 +1,20 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseUnlockHomeBgm extends ItemUseInt { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_UNLOCK_HOME_BGM; + } + + public ItemUseUnlockHomeBgm(String[] useParam) { + super(useParam); + } + + @Override + public boolean useItem(UseItemParams params) { + params.player.getHome().addUnlockedHomeBgm(this.i); + return true; // Probably best to remove the item even if the bgm was already unlocked. + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockHomeModule.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockHomeModule.java new file mode 100644 index 000000000..5e69cb884 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockHomeModule.java @@ -0,0 +1,19 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseUnlockHomeModule extends ItemUseInt { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_UNLOCK_HOME_MODULE; + } + + public ItemUseUnlockHomeModule(String[] useParam) { + super(useParam); + } + + @Override + public boolean useItem(UseItemParams params) { + return false; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockPaidBattlePassNormal.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockPaidBattlePassNormal.java new file mode 100644 index 000000000..5b34bc6c7 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/ItemUseUnlockPaidBattlePassNormal.java @@ -0,0 +1,20 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.props.ItemUseOp; + +public class ItemUseUnlockPaidBattlePassNormal extends ItemUseAction { + @Override + public ItemUseOp getItemUseOp() { + return ItemUseOp.ITEM_USE_UNLOCK_PAID_BATTLE_PASS_NORMAL; + } + + public ItemUseUnlockPaidBattlePassNormal(String[] useParam) { + } + + @Override + public boolean useItem(UseItemParams params) { + // TODO: add paid BP + //return params.player.getBattlePassManager().setPaid(true); + return false; + } +} diff --git a/src/main/java/emu/grasscutter/game/props/ItemUseAction/UseItemParams.java b/src/main/java/emu/grasscutter/game/props/ItemUseAction/UseItemParams.java new file mode 100644 index 000000000..3fc3cc5be --- /dev/null +++ b/src/main/java/emu/grasscutter/game/props/ItemUseAction/UseItemParams.java @@ -0,0 +1,29 @@ +package emu.grasscutter.game.props.ItemUseAction; + +import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.props.ItemUseTarget; + +public class UseItemParams { + // Don't want to change 40 method signatures when this gets extended! + public Player player; + public ItemUseTarget itemUseTarget; + public Avatar targetAvatar = null; + public int count = 1; + public int optionId = 0; + public boolean isEnterMpDungeonTeam = false; + + public UseItemParams(Player player, ItemUseTarget itemUseTarget, Avatar targetAvatar, int count, int optionId, boolean isEnterMpDungeonTeam) { + this.player = player; + this.itemUseTarget = itemUseTarget; + this.targetAvatar = targetAvatar; + this.count = count; + this.optionId = optionId; + this.isEnterMpDungeonTeam = isEnterMpDungeonTeam; + } + + public UseItemParams(Player player, ItemUseTarget itemUseTarget) { + this.player = player; + this.itemUseTarget = itemUseTarget; + } +} diff --git a/src/main/java/emu/grasscutter/game/shop/ShopChestTable.java b/src/main/java/emu/grasscutter/game/shop/ShopChestTable.java deleted file mode 100644 index d4b850b54..000000000 --- a/src/main/java/emu/grasscutter/game/shop/ShopChestTable.java +++ /dev/null @@ -1,27 +0,0 @@ -package emu.grasscutter.game.shop; - -import emu.grasscutter.data.common.ItemParamData; - -import java.util.ArrayList; -import java.util.List; - -public class ShopChestTable { - private int itemId; - private List containsItem = new ArrayList<>(); - - public int getItemId() { - return itemId; - } - - public void setItemId(int itemId) { - this.itemId = itemId; - } - - public List getContainsItem() { - return containsItem; - } - - public void setContainsItem(List containsItem) { - this.containsItem = containsItem; - } -} diff --git a/src/main/java/emu/grasscutter/game/shop/ShopSystem.java b/src/main/java/emu/grasscutter/game/shop/ShopSystem.java index cae941752..8253e05aa 100644 --- a/src/main/java/emu/grasscutter/game/shop/ShopSystem.java +++ b/src/main/java/emu/grasscutter/game/shop/ShopSystem.java @@ -3,6 +3,7 @@ package emu.grasscutter.game.shop; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.excels.ShopGoodsData; import emu.grasscutter.server.game.BaseGameSystem; import emu.grasscutter.server.game.GameServer; @@ -14,10 +15,11 @@ import static emu.grasscutter.config.Configuration.*; import java.util.ArrayList; import java.util.List; +import java.util.Map; public class ShopSystem extends BaseGameSystem { private final Int2ObjectMap> shopData; - private final List shopChestData; + private final Int2ObjectMap> shopChestData; private static final int REFRESH_HOUR = 4; // In GMT+8 server private static final String TIME_ZONE = "Asia/Shanghai"; // GMT+8 Timezone @@ -25,7 +27,7 @@ public class ShopSystem extends BaseGameSystem { public ShopSystem(GameServer server) { super(server); this.shopData = new Int2ObjectOpenHashMap<>(); - this.shopChestData = new ArrayList<>(); + this.shopChestData = new Int2ObjectOpenHashMap<>(); this.load(); } @@ -33,8 +35,8 @@ public class ShopSystem extends BaseGameSystem { return shopData; } - public List getShopChestData() { - return shopChestData; + public List getShopChestData(int chestId) { + return this.shopChestData.get(chestId); } public static int getShopNextRefreshTime(ShopInfo shopInfo) { @@ -76,15 +78,22 @@ public class ShopSystem extends BaseGameSystem { } private void loadShopChest() { - getShopChestData().clear(); + shopChestData.clear(); try { - List shopChestTableList = DataLoader.loadList("ShopChest.json", ShopChestTable.class); - if (shopChestTableList.size() > 0) { - getShopChestData().addAll(shopChestTableList); - Grasscutter.getLogger().debug("ShopChest data successfully loaded."); - } else { - Grasscutter.getLogger().error("Unable to load ShopChest data. ShopChest data size is 0."); - } + Map chestMap = DataLoader.loadMap("ShopChest.v2.json", Integer.class, String.class); + chestMap.forEach((chestId, itemStr) -> { + if (itemStr.isEmpty()) return; + var entries = itemStr.split(","); + var list = new ArrayList(entries.length); + for (var entry : entries) { + var idAndCount = entry.split(":"); + int id = Integer.parseInt(idAndCount[0]); + int count = Integer.parseInt(idAndCount[1]); + list.add(new ItemParamData(id, count)); + } + this.shopChestData.put((int) chestId, list); + }); + Grasscutter.getLogger().debug("Loaded " + chestMap.size() + " ShopChest entries."); } catch (Exception e) { Grasscutter.getLogger().error("Unable to load ShopChest data.", e); } diff --git a/src/main/java/emu/grasscutter/game/systems/InventorySystem.java b/src/main/java/emu/grasscutter/game/systems/InventorySystem.java index 39f267a1a..5c2afa758 100644 --- a/src/main/java/emu/grasscutter/game/systems/InventorySystem.java +++ b/src/main/java/emu/grasscutter/game/systems/InventorySystem.java @@ -2,26 +2,33 @@ package emu.grasscutter.game.systems; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; +import java.util.stream.Stream; +import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.common.ItemParamData; -import emu.grasscutter.data.common.ItemUseData; import emu.grasscutter.data.excels.AvatarPromoteData; import emu.grasscutter.data.excels.AvatarSkillDepotData; import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.data.excels.WeaponPromoteData; -import emu.grasscutter.data.excels.AvatarSkillDepotData.InherentProudSkillOpens; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.ItemType; +import emu.grasscutter.game.inventory.MaterialType; import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.props.ActionReason; +import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.ItemUseOp; -import emu.grasscutter.game.props.ItemUseTarget; -import emu.grasscutter.game.shop.ShopChestTable; +import emu.grasscutter.game.props.ItemUseAction.ItemUseAddExp; +import emu.grasscutter.game.props.ItemUseAction.ItemUseAddReliquaryExp; +import emu.grasscutter.game.props.ItemUseAction.ItemUseAddWeaponExp; +import emu.grasscutter.game.props.ItemUseAction.UseItemParams; import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; import emu.grasscutter.net.proto.MaterialInfoOuterClass.MaterialInfo; import emu.grasscutter.server.event.player.PlayerUseFoodEvent; @@ -29,28 +36,32 @@ import emu.grasscutter.server.game.BaseGameSystem; import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.Utils; +import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2IntRBTreeMap; +import lombok.val; public class InventorySystem extends BaseGameSystem { - private final static int RELIC_MATERIAL_1 = 105002; // Sanctifying Unction - private final static int RELIC_MATERIAL_2 = 105003; // Sanctifying Essence - private final static int RELIC_MATERIAL_EXP_1 = 2500; // Sanctifying Unction - private final static int RELIC_MATERIAL_EXP_2 = 10000; // Sanctifying Essence - - private final static int WEAPON_ORE_1 = 104011; // Enhancement Ore - private final static int WEAPON_ORE_2 = 104012; // Fine Enhancement Ore - private final static int WEAPON_ORE_3 = 104013; // Mystic Enhancement Ore - private final static int WEAPON_ORE_EXP_1 = 400; // Enhancement Ore - private final static int WEAPON_ORE_EXP_2 = 2000; // Fine Enhancement Ore - private final static int WEAPON_ORE_EXP_3 = 10000; // Mystic Enhancement Ore - - private final static int AVATAR_BOOK_1 = 104001; // Wanderer's Advice - private final static int AVATAR_BOOK_2 = 104002; // Adventurer's Experience - private final static int AVATAR_BOOK_3 = 104003; // Hero's Wit - private final static int AVATAR_BOOK_EXP_1 = 1000; // Wanderer's Advice - private final static int AVATAR_BOOK_EXP_2 = 5000; // Adventurer's Experience - private final static int AVATAR_BOOK_EXP_3 = 20000; // Hero's Wit + private static final Int2IntMap weaponRefundMaterials = new Int2IntArrayMap(); + { + // Use a sorted map, use exp as key to sort by exp + // We want to have weaponRefundMaterials as (id, exp) in descending exp order + var temp = new Int2IntRBTreeMap(Collections.reverseOrder()); + GameData.getItemDataMap().forEach((id, data) -> { + if (data == null) return; + if (data.getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) return; + var actions = data.getItemUseActions(); + if (actions == null) return; + for (var action : actions) { + if (action.getItemUseOp() == ItemUseOp.ITEM_USE_ADD_WEAPON_EXP) { + temp.putIfAbsent((int) ((ItemUseAddWeaponExp) action).getExp(), (int) id); + return; + } + } + }); + temp.forEach((exp, id) -> weaponRefundMaterials.putIfAbsent((int) id, (int) exp)); + } public InventorySystem(GameServer server) { super(server); @@ -88,8 +99,9 @@ public class InventorySystem extends BaseGameSystem { continue; } // Calculate mora cost - moraCost += food.getItemData().getBaseConvExp(); - expGain += food.getItemData().getBaseConvExp(); + int exp = food.getItemData().getBaseConvExp(); + moraCost += exp; + expGain += exp; // Feeding artifact with exp already if (food.getTotalExp() > 0) { expGain += (food.getTotalExp() * 4) / 5; @@ -99,11 +111,19 @@ public class InventorySystem extends BaseGameSystem { List payList = new ArrayList(); for (ItemParam itemParam : list) { int amount = itemParam.getCount(); // Previously this capped to inventory amount, but rejecting the payment makes more sense for an invalid order - int gain = amount * switch (itemParam.getItemId()) { - case RELIC_MATERIAL_1 -> RELIC_MATERIAL_EXP_1; - case RELIC_MATERIAL_2 -> RELIC_MATERIAL_EXP_2; - default -> 0; - }; + int gain = 0; + var data = GameData.getItemDataMap().get(itemParam.getItemId()); + if (data != null) { + var actions = data.getItemUseActions(); + if (actions != null) { + for (var action : actions) { + if (action.getItemUseOp() == ItemUseOp.ITEM_USE_ADD_RELIQUARY_EXP) { + gain += ((ItemUseAddReliquaryExp) action).getExp(); + } + } + } + } + gain *= amount; expGain += gain; moraCost += gain; payList.add(new ItemParamData(itemParam.getItemId(), itemParam.getCount())); @@ -197,25 +217,39 @@ public class InventorySystem extends BaseGameSystem { } // Get exp gain - int expGain = 0; - for (long guid : foodWeaponGuidList) { - GameItem food = player.getInventory().getItemByGuid(guid); - if (food == null) { - continue; - } - expGain += food.getItemData().getWeaponBaseExp(); - if (food.getTotalExp() > 0) { - expGain += (food.getTotalExp() * 4) / 5; - } - } - for (ItemParam param : itemParamList) { - expGain += param.getCount() * switch (param.getItemId()) { - case WEAPON_ORE_1 -> WEAPON_ORE_EXP_1; - case WEAPON_ORE_2 -> WEAPON_ORE_EXP_2; - case WEAPON_ORE_3 -> WEAPON_ORE_EXP_3; - default -> 0; - }; - } + int expGain = foodWeaponGuidList.stream() + .map(player.getInventory()::getItemByGuid) + .filter(Objects::nonNull) + .mapToInt(food -> food.getItemData().getWeaponBaseExp() + ((food.getTotalExp() * 4) / 5)) + .sum(); + // Stream::ofNullable version + expGain += itemParamList.stream() + .mapToInt(param -> { + int exp = Stream.ofNullable(GameData.getItemDataMap().get(param.getItemId())) + .map(ItemData::getItemUseActions) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .filter(action -> action.getItemUseOp() == ItemUseOp.ITEM_USE_ADD_WEAPON_EXP) + .mapToInt(action -> ((ItemUseAddWeaponExp) action).getExp()) + .sum(); + return exp * param.getCount(); + }) + .sum(); + // Optional::ofNullable version + // expGain += itemParamList.stream() + // .mapToInt(param -> { + // int exp = Optional.ofNullable(GameData.getItemDataMap().get(param.getItemId())) + // .map(ItemData::getItemUseActions) + // .map(actions -> { + // return actions.stream() + // .filter(action -> action.getItemUseOp() == ItemUseOp.ITEM_USE_ADD_WEAPON_EXP) + // .mapToInt(action -> ((ItemUseAddWeaponExp) action).getExp()) + // .sum(); + // }) + // .orElse(0); + // return exp * param.getCount(); + // }) + // .sum(); // Try int maxLevel = promoteData.getUnlockMaxLevel(); @@ -272,13 +306,19 @@ public class InventorySystem extends BaseGameSystem { List payList = new ArrayList(); for (ItemParam param : itemParamList) { int amount = param.getCount(); // Previously this capped to inventory amount, but rejecting the payment makes more sense for an invalid order - int gain = amount * switch (param.getItemId()) { - case WEAPON_ORE_1 -> WEAPON_ORE_EXP_1; - case WEAPON_ORE_2 -> WEAPON_ORE_EXP_2; - case WEAPON_ORE_3 -> WEAPON_ORE_EXP_3; - default -> 0; - }; - expGain += gain; + + var data = GameData.getItemDataMap().get(param.getItemId()); + if (data != null) { + var actions = data.getItemUseActions(); + if (actions != null) { + for (var action : actions) { + if (action.getItemUseOp() == ItemUseOp.ITEM_USE_ADD_WEAPON_EXP) { + expGain += ((ItemUseAddWeaponExp) action).getExp() * amount; + } + } + } + } + payList.add(new ItemParamData(param.getItemId(), amount)); } @@ -344,23 +384,13 @@ public class InventorySystem extends BaseGameSystem { private List getLeftoverOres(int leftover) { List leftoverOreList = new ArrayList<>(3); - if (leftover < WEAPON_ORE_EXP_1) { - return leftoverOreList; - } + for (var e : weaponRefundMaterials.int2IntEntrySet()) { + int exp = e.getIntValue(); + int ores = leftover / exp; + leftover = leftover % exp; - // Get leftovers - int ore3 = leftover / WEAPON_ORE_EXP_3; - leftover = leftover % WEAPON_ORE_EXP_3; - int ore2 = leftover / WEAPON_ORE_EXP_2; - leftover = leftover % WEAPON_ORE_EXP_2; - int ore1 = leftover / WEAPON_ORE_EXP_1; - - if (ore3 > 0) { - leftoverOreList.add(ItemParam.newBuilder().setItemId(WEAPON_ORE_3).setCount(ore3).build()); - } if (ore2 > 0) { - leftoverOreList.add(ItemParam.newBuilder().setItemId(WEAPON_ORE_2).setCount(ore2).build()); - } if (ore1 > 0) { - leftoverOreList.add(ItemParam.newBuilder().setItemId(WEAPON_ORE_1).setCount(ore1).build()); + if (ores > 0) + leftoverOreList.add(ItemParam.newBuilder().setItemId(e.getIntKey()).setCount(ores).build()); } return leftoverOreList; @@ -503,22 +533,18 @@ public class InventorySystem extends BaseGameSystem { avatar.setPromoteLevel(nextPromoteLevel); // Update proud skills - AvatarSkillDepotData skillDepot = GameData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId()); - - if (skillDepot != null && skillDepot.getInherentProudSkillOpens() != null) { - for (InherentProudSkillOpens openData : skillDepot.getInherentProudSkillOpens()) { - if (openData.getProudSkillGroupId() == 0) { - continue; - } - if (openData.getNeedAvatarPromoteLevel() == avatar.getPromoteLevel()) { - int proudSkillId = (openData.getProudSkillGroupId() * 100) + 1; - if (GameData.getProudSkillDataMap().containsKey(proudSkillId)) { - avatar.getProudSkillList().add(proudSkillId); - player.sendPacket(new PacketProudSkillChangeNotify(avatar)); - } - } - } - } + Optional.ofNullable(GameData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId())) + .map(AvatarSkillDepotData::getInherentProudSkillOpens) + .ifPresent(d -> d.stream() + .filter(openData -> openData.getProudSkillGroupId() > 0) + .filter(openData -> openData.getNeedAvatarPromoteLevel() == avatar.getPromoteLevel()) + .mapToInt(openData -> (openData.getProudSkillGroupId() * 100) + 1) + .filter(GameData.getProudSkillDataMap()::containsKey) + .forEach(proudSkillId -> { + avatar.getProudSkillList().add(proudSkillId); + player.sendPacket(new PacketProudSkillChangeNotify(avatar)); + }) + ); // Packets player.sendPacket(new PacketAvatarPropNotify(avatar)); @@ -543,12 +569,19 @@ public class InventorySystem extends BaseGameSystem { } // Calc exp - int expGain = switch (itemId) { - case AVATAR_BOOK_1 -> AVATAR_BOOK_EXP_1 * count; - case AVATAR_BOOK_2 -> AVATAR_BOOK_EXP_2 * count; - case AVATAR_BOOK_3 -> AVATAR_BOOK_EXP_3 * count; - default -> 0; - }; + int expGain = 0; + + var data = GameData.getItemDataMap().get(itemId); + if (data != null) { + var actions = data.getItemUseActions(); + if (actions != null) { + for (var action : actions) { + if (action.getItemUseOp() == ItemUseOp.ITEM_USE_ADD_EXP) { + expGain += ((ItemUseAddExp) action).getExp() * count; + } + } + } + } // Sanity check if (expGain <= 0) { @@ -699,187 +732,85 @@ public class InventorySystem extends BaseGameSystem { player.sendPacket(new PacketDestroyMaterialRsp(returnMaterialMap)); } - public GameItem useItem(Player player, long targetGuid, long itemGuid, int count, int optionId) { + // Uses an item from the player's inventory. + public synchronized GameItem useItem(Player player, long targetGuid, long itemGuid, int count, int optionId, boolean isEnterMpDungeonTeam) { + Grasscutter.getLogger().info("Attempting to use item from inventory"); Avatar target = player.getAvatars().getAvatarByGuid(targetGuid); - GameItem useItem = player.getInventory().getItemByGuid(itemGuid); - - if (useItem == null) { - return null; - } - - int used = 0; - boolean useSuccess = false; - ItemData itemData = useItem.getItemData(); + GameItem item = player.getInventory().getItemByGuid(itemGuid); + if (item == null) return null; + if (item.getCount() < count) return null; + ItemData itemData = item.getItemData(); if (itemData == null) return null; - // Use - switch (itemData.getMaterialType()) { - case MATERIAL_FOOD: - if (itemData.getUseTarget() == ItemUseTarget.ITEM_USE_TARGET_SPECIFY_DEAD_AVATAR) { - if (target == null) { - break; - } - - // Invoke player use food event. - PlayerUseFoodEvent event = new PlayerUseFoodEvent(player, itemData, target.getAsEntity()); - // Call the event. - event.call(); if (!event.isCanceled()) { - used = player.getTeamManager().reviveAvatar(target) ? 1 : 0; - } - } else { - used = 1; - } - break; - case MATERIAL_NOTICE_ADD_HP: - if (itemData.getUseTarget() == ItemUseTarget.ITEM_USE_TARGET_SPECIFY_ALIVE_AVATAR) { - if (target == null) { - break; - } - - // Invoke player use food event. - PlayerUseFoodEvent event = new PlayerUseFoodEvent(player, itemData, target.getAsEntity()); - // Call the event. - event.call(); if (!event.isCanceled()) { - int[] SatiationParams = itemData.getSatiationParams(); - used = player.getTeamManager().healAvatar(target, SatiationParams[0], SatiationParams[1]) ? 1 : 0; - } - } - break; - case MATERIAL_CONSUME: - // Make sure we have usage data for this material. - if (itemData.getItemUse() == null) { - break; - } - - ItemUseOp useOp = itemData.getItemUse().get(0).getUseOp(); - - // Unlock item based on use operation - useSuccess = switch (useOp) { - case ITEM_USE_UNLOCK_FORGE -> player.getForgingManager().unlockForgingBlueprint(useItem); - case ITEM_USE_UNLOCK_COMBINE -> player.getServer().getCombineSystem().unlockCombineDiagram(player, useItem); - case ITEM_USE_UNLOCK_COOK_RECIPE -> player.getCookingManager().unlockRecipe(useItem); - default -> useSuccess; - }; - break; - case MATERIAL_FURNITURE_FORMULA: - case MATERIAL_FURNITURE_SUITE_FORMULA: - if (itemData.getItemUse() == null) { - break; - } - useSuccess = player.getFurnitureManager().unlockFurnitureOrSuite(useItem); - - break; - case MATERIAL_CONSUME_BATCH_USE: - // Make sure we have usage data for this material. - if (itemData.getItemUse() == null) { - break; - } - - // Handle fragile/transient resin. - if (useItem.getItemId() == 107009 || useItem.getItemId() == 107012) { - // Add resin to the inventory. - ItemData resinItemData = GameData.getItemDataMap().get(106); - player.getInventory().addItem(new GameItem(resinItemData, 60 * count), ActionReason.PlayerUseItem); - - // Set used amount. - used = count; - } - break; - case MATERIAL_CHEST: - List shopChestTableList = player.getServer().getShopSystem().getShopChestData(); - List rewardItemList = new ArrayList<>(); - for (ShopChestTable shopChestTable : shopChestTableList) { - if (shopChestTable.getItemId() != useItem.getItemId()) { - continue; - } - - if (shopChestTable.getContainsItem() == null) { - break; - } - - for (ItemParamData itemParamData : shopChestTable.getContainsItem()) { - ItemData containedItem = GameData.getItemDataMap().get(itemParamData.getId()); - if (containedItem == null) { - continue; - } - rewardItemList.add(new GameItem(containedItem, itemParamData.getCount())); - } - - if (!rewardItemList.isEmpty()) { - player.getInventory().addItems(rewardItemList, ActionReason.Shop); - } - - used = 1; - break; - } - break; - case MATERIAL_CHEST_BATCH_USE: - if (optionId < 1) return null; // 1-indexed selection - for (var use : itemData.getItemUse()) { - if (use.getUseOp() != ItemUseOp.ITEM_USE_CHEST_SELECT_ITEM) continue; - String[] choices = use.getUseParam()[0].split(","); - if (optionId > choices.length) return null; - String[] choiceParts = choices[optionId-1].split(":"); - int optionItemId = Integer.parseInt(choiceParts[0]); - int optionItemCount = Integer.parseInt(choiceParts[1]); - ItemData optionItem = GameData.getItemDataMap().get(optionItemId); - if (optionItem == null) { - break; - } - - player.getInventory().addItem(new GameItem(optionItem, optionItemCount * count), ActionReason.Shop); - - used = count; - break; - } - break; - case MATERIAL_BGM: - ItemUseData use = itemData.getItemUse().get(0); - if (use.getUseOp() == ItemUseOp.ITEM_USE_UNLOCK_HOME_BGM) { - int bgmId = Integer.parseInt(use.getUseParam()[0]); - player.getInventory().removeItem(useItem, 1); - player.sendPacket(new PacketUnlockHomeBgmNotify(bgmId)); - player.getHome().addUnlockedHomeBgm(bgmId); - player.sendPacket(new PacketUnlockedHomeBgmNotify(player)); - return useItem; - } - break; - default: - break; + var params = new UseItemParams(player, itemData.getUseTarget(), target, count, optionId, isEnterMpDungeonTeam); + if (useItemDirect(itemData, params)) { + player.getInventory().removeItem(item, count); + var actions = itemData.getItemUseActions(); + if (actions != null) + actions.forEach(use -> use.postUseItem(params)); + Grasscutter.getLogger().info("Item use succeeded!"); + return item; + } else { + Grasscutter.getLogger().info("Item use failed!"); + return null; } + } - // Welkin - if (useItem.getItemId() == 1202) { - player.rechargeMoonCard(); - used = 1; - } + // Uses an item without checking the player's inventory. + public synchronized boolean useItemDirect(ItemData itemData, UseItemParams params) { + if (itemData == null) return false; - // If we used at least one item, or one of the methods called here reports using the item successfully, - // we return the item to make UseItemRsp a success. - if (used > 0) { - // Handle use params, mainly server buffs - for (ItemUseData useData : itemData.getItemUse()) { - switch (useData.getUseOp()) { - case ITEM_USE_ADD_SERVER_BUFF -> { - int buffId = Integer.parseInt(useData.getUseParam()[0]); - String timeString = useData.getUseParam()[1]; - float time = timeString.isEmpty() ? 0 : Float.parseFloat(timeString); - - player.getBuffManager().addBuff(buffId, time); - } - default -> {} - } + // Ensure targeting conditions are satisfied + val target = Optional.ofNullable(params.targetAvatar); + switch (params.itemUseTarget) { + case ITEM_USE_TARGET_NONE -> {} + case ITEM_USE_TARGET_SPECIFY_AVATAR -> { + if (target.isEmpty()) return false; } - - // Remove item from inventory since we used it - player.getInventory().removeItem(useItem, used); - return useItem; + case ITEM_USE_TARGET_SPECIFY_ALIVE_AVATAR -> { + if (target.map(a -> !a.getAsEntity().isAlive()).orElse(true)) return false; + } + case ITEM_USE_TARGET_SPECIFY_DEAD_AVATAR -> { + if (target.map(a -> a.getAsEntity().isAlive()).orElse(true)) return false; + } + case ITEM_USE_TARGET_CUR_AVATAR -> {} + case ITEM_USE_TARGET_CUR_TEAM -> {} } - if (useSuccess) { - return useItem; + int[] satiationParams = itemData.getSatiationParams(); + if (satiationParams != null && target.isPresent()) { + // Invoke and call player use food event. + var event = new PlayerUseFoodEvent(params.player, itemData, params.targetAvatar.getAsEntity()); event.call(); + if (event.isCanceled()) return false; + + float satiationIncrease = satiationParams[0] + ((float)satiationParams[1])/params.targetAvatar.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + if (!params.targetAvatar.addSatiation(satiationIncrease)) { // Make sure avatar can eat + return false; + } } - return null; + // Use + var actions = itemData.getItemUseActions(); + Grasscutter.getLogger().info("Using - actions - {}", actions); + if (actions == null) return true; // Maybe returning false would be more appropriate? + return actions.stream() + .map(use -> use.useItem(params)) + .reduce(false, (a,b) -> a || b); // Don't short-circuit!!! + } + + public static synchronized int checkPlayerAvatarConstellationLevel(Player player, int itemId) { + 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 += Optional.ofNullable(constItem).map(GameItem::getCount).orElse(0); + return constLevel; } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeHomeBgmReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeHomeBgmReq.java index 98cbbf26a..ea73264e8 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeHomeBgmReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerChangeHomeBgmReq.java @@ -7,7 +7,6 @@ import emu.grasscutter.net.proto.Unk2700BEDLIGJANCJClientReq; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.PacketChangeHomeBgmNotify; import emu.grasscutter.server.packet.send.PacketChangeHomeBgmRsp; -import emu.grasscutter.server.packet.send.PacketUnlockedHomeBgmNotify; @Opcodes(PacketOpcodes.Unk2700_BEDLIGJANCJ_ClientReq) public class HandlerChangeHomeBgmReq extends PacketHandler { @@ -17,11 +16,8 @@ public class HandlerChangeHomeBgmReq extends PacketHandler { int homeBgmId = req.getUnk2700BJHAMKKECEI(); var home = session.getPlayer().getHome(); - if (!home.getUnlockedHomeBgmListInfo().contains(homeBgmId)) { - home.addUnlockedHomeBgm(homeBgmId); - session.send(new PacketUnlockedHomeBgmNotify(session.getPlayer())); - } + home.addUnlockedHomeBgm(homeBgmId); // Not sure if this is sane home.getHomeSceneItem(session.getPlayer().getSceneId()).setHomeBgmId(homeBgmId); home.save(); diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerUseItemReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerUseItemReq.java index 54314959d..4d422fe7b 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerUseItemReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerUseItemReq.java @@ -15,7 +15,7 @@ public class HandlerUseItemReq extends PacketHandler { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { UseItemReq req = UseItemReq.parseFrom(payload); - GameItem useItem = session.getServer().getInventorySystem().useItem(session.getPlayer(), req.getTargetGuid(), req.getGuid(), req.getCount(), req.getOptionIdx()); + GameItem useItem = session.getServer().getInventorySystem().useItem(session.getPlayer(), req.getTargetGuid(), req.getGuid(), req.getCount(), req.getOptionIdx(), req.getIsEnterMpDungeonTeam()); if (useItem != null) { session.send(new PacketUseItemRsp(req.getTargetGuid(), useItem)); } else { diff --git a/src/main/resources/defaults/data/ShopChest.json b/src/main/resources/defaults/data/ShopChest.json deleted file mode 100644 index b9d6b2359..000000000 --- a/src/main/resources/defaults/data/ShopChest.json +++ /dev/null @@ -1,153 +0,0 @@ -[ - { - "itemId": 115019, - "containsItem": [ - { - "Id": 104002, - "Count": 40 - }, - { - "Id": 202, - "Count": 30000 - } - ] - }, - { - "itemId": 115020, - "containsItem": [ - { - "Id": 104013, - "Count": 25 - }, - { - "Id": 202, - "Count": 30000 - } - ] - }, - { - "itemId": 115021, - "containsItem": [ - { - "Id": 115013, - "Count": 5 - }, - { - "Id": 104003, - "Count": 40 - }, - { - "Id": 202, - "Count": 120000 - } - ] - }, - { - "itemId": 115022, - "containsItem": [ - { - "Id": 115017, - "Count": 25 - }, - { - "Id": 202, - "Count": 150000 - } - ] - }, - { - "itemId": 115023, - "containsItem": [ - { - "Id": 115025, - "Count": 10 - }, - { - "Id": 202, - "Count": 60000 - } - ] - }, - { - "itemId": 115029, - "containsItem": [ - { - "Id": 104013, - "Count": 100 - }, - { - "Id": 202, - "Count": 100000 - } - ] - }, - { - "itemId": 115030, - "containsItem": [ - { - "Id": 104003, - "Count": 12 - }, - { - "Id": 202, - "Count": 10000 - } - ] - }, - { - "itemId": 115034, - "containsItem": [ - { - "Id": 115013, - "Count": 6 - }, - { - "Id": 202, - "Count": 60000 - } - ] - }, - { - "itemId": 115032, - "containsItem": [ - { - "Id": 115024, - "Count": 12 - } - ] - }, - { - "itemId": 115010, - "containsItem": [ - { - "Id": 104002, - "Count": 80 - }, - { - "Id": 104012, - "Count": 40 - } - ] - }, - { - "itemId": 115011, - "containsItem": [ - { - "Id": 104003, - "Count": 50 - }, - { - "Id": 104013, - "Count": 25 - }, - { - "Id": 107009, - "Count": 1 - }, - { - "Id": 202, - "Count": 50000 - } - ] - } -] diff --git a/src/main/resources/defaults/data/ShopChest.v2.json b/src/main/resources/defaults/data/ShopChest.v2.json new file mode 100644 index 000000000..2b258f62f --- /dev/null +++ b/src/main/resources/defaults/data/ShopChest.v2.json @@ -0,0 +1,29 @@ +{ + "212000002":"", + "1000100":"", + "7000600":"", + "7000700":"", + "7000800":"", + "7000900":"", + "7001000":"", + "7001100":"", + "7001200":"", + "7001300":"", + "7001400":"", + + "23010000": "104002:80,104012:40", + "23010001": "104003:50,104013:25,107009:1,202:50000", + "23010100": "104002:40,202:30000", + "23010101": "104013:25,202:30000", + "23010102": "115013:5,104003:40,202:120000", + "23010103": "115017:25,202:150000", + "23010104": "115025:10,202:60000", + "23010105": "104013:100,202:100000", + "23010106": "104003:12,202:10000", + "23010107": "115024:12", + "23010109": "115013:6,202:60000", + "23020000": "109449:1,109450:1", + "23020001": "373345:1", + "23020002": "373341:1", + "23020003": "373342:1" +}