From 2fa274624647cc1bd40a11ff6e6ef8260c500378 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Wed, 11 May 2022 07:22:19 -0700 Subject: [PATCH 1/8] Update StaminaManager 1. Update function signatures to prepare for vehicle stamina. 3. Remove hard-coded skills. 2. Wind resonance -15% stamina cost. 4. Climb talent cost reduction. 5. Swim talent cost reduction. 6. Diluc will now consume stamina at full price if talent not activated. 7. Sayu's windwheel no longer consumes stamina. --- .../java/emu/grasscutter/Grasscutter.java | 4 + .../StaminaManager/StaminaManager.java | 275 +++++++++--------- .../send/PacketVehicleStaminaNotify.java | 19 ++ 3 files changed, 157 insertions(+), 141 deletions(-) create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketVehicleStaminaNotify.java diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 6ca78dfa9..2af2415ab 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -4,6 +4,7 @@ import java.io.*; import java.util.Calendar; import emu.grasscutter.command.CommandMap; +import emu.grasscutter.game.managers.StaminaManager.StaminaManager; import emu.grasscutter.plugin.PluginManager; import emu.grasscutter.plugin.api.ServerHook; import emu.grasscutter.scripts.ScriptLoader; @@ -110,6 +111,9 @@ public final class Grasscutter { new ServerHook(gameServer, dispatchServer); // Create plugin manager instance. pluginManager = new PluginManager(); + + // TODO: find a better place? + StaminaManager.initialize(); // Start servers. var runMode = SERVER.runMode; diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index 7a949b8ab..e076f5856 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -2,6 +2,7 @@ package emu.grasscutter.game.managers.StaminaManager; import ch.qos.logback.classic.Logger; import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.player.Player; @@ -18,16 +19,15 @@ import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.Position; import org.jetbrains.annotations.NotNull; -import java.lang.Math; import java.util.*; -import static emu.grasscutter.Configuration.*; +import static emu.grasscutter.Configuration.GAME_OPTIONS; public class StaminaManager { // TODO: Skiff state detection? private final Player player; - private final HashMap> MotionStatesCategorized = new HashMap<>() {{ + private static final HashMap> MotionStatesCategorized = new HashMap<>() {{ put("CLIMB", new HashSet<>(List.of( MotionState.MOTION_CLIMB, // sustained, when not moving no cost no recover MotionState.MOTION_STANDBY_TO_CLIMB // NOT OBSERVED, see MOTION_JUMP_UP_WALL_FOR_STANDBY @@ -122,74 +122,67 @@ public class StaminaManager { private int lastSkillId = 0; private int lastSkillCasterId = 0; private boolean lastSkillFirstTick = true; - public static final HashSet TalentMovements = new HashSet<>(List.of( + private static final HashSet TalentMovements = new HashSet<>(List.of( 10013, // Kamisato Ayaka 10413 // Mona )); + private static final HashMap ClimbFoodReductionMap = new HashMap<>() {{ + // TODO: get real food id + put(0, 0.8f); // Sample food + }}; + private static final HashMap DashFoodReductionMap = new HashMap<>() {{ + // TODO: get real food id + put(0, 0.8f); // Sample food + }}; + private static final HashMap FlyFoodReductionMap = new HashMap<>() {{ + // TODO: get real food id + put(0, 0.8f); // Sample food + }}; + private static final HashMap SwimFoodReductionMap = new HashMap<>() {{ + // TODO: get real food id + put(0, 0.8f); // Sample food + }}; + private static final HashMap ClimbTalentReductionMap = new HashMap<>() {{ + put(262301, 0.8f); // Xiao + }}; + private static final HashMap FlyTalentReductionMap = new HashMap<>() {{ + put(212301, 0.8f); // Amber + put(222301, 0.8f); // Venti + }}; + private static final HashMap SwimTalentReductionMap = new HashMap<>() {{ + put(242301, 0.8f); // Beidou + put(542301, 0.8f); // Sangonomiya Kokomi + }}; - // TODO: Get from somewhere else, instead of hard-coded here? - public static final HashSet ClaymoreSkills = new HashSet<>(List.of( - 10160, // Diluc, /=2 - 10201, // Razor - 10241, // Beidou - 10341, // Noelle - 10401, // Chongyun - 10441, // Xinyan - 10511, // Eula - 10531, // Sayu - 10571 // Arataki Itto, = 0 - )); - public static final HashSet CatalystSkills = new HashSet<>(List.of( - 10060, // Lisa - 10070, // Barbara - 10271, // Ningguang - 10291, // Klee - 10411, // Mona - 10431, // Sucrose - 10481, // Yanfei - 10541, // Sangonomoiya Kokomi - 10581 // Yae Miko - )); - public static final HashSet PolearmSkills = new HashSet<>(List.of( - 10231, // Xiangling - 10261, // Xiao - 10301, // Zhongli - 10451, // Rosaria - 10461, // Hu Tao - 10501, // Thoma - 10521, // Raiden Shogun - 10631, // Shenhe - 10641 // Yunjin - )); - public static final HashSet SwordSkills = new HashSet<>(List.of( - 10024, // Kamisato Ayaka - 10031, // Jean - 10073, // Kaeya - 10321, // Bennett - 10337, // Tartaglia, melee stance (10332 switch to melee, 10336 switch to ranged stance) - 10351, // Qiqi - 10381, // Xingqiu - 10386, // Albedo - 10421, // Keqing, =-2500 - 10471, // Kaedehara Kazuha - 10661, // Kamisato Ayato - 100553, // Lumine - 100540 // Aether - )); - public static final HashSet BowSkills = new HashSet<>(List.of( - 10041, 10043, // Amber - 10221, 10223,// Venti - 10311, 10315, // Fischl - 10331, 10335, // Tartaglia, ranged stance - 10371, // Ganyu - 10391, 10394, // Diona - 10491, // Yoimiya - 10551, 10554, // Gorou - 10561, 10564, // Kojou Sara - 10621, // Aloy - 99998, 99999 // Yelan // TODO: get real values - )); + public static final HashSet BowAvatars = new HashSet<>(); + public static final HashSet CatalystAvatars = new HashSet<>(); + public static final HashSet ClaymoreAvatars = new HashSet<>(); + public static final HashSet PolearmAvatars = new HashSet<>(); + public static final HashSet SwordAvatars = new HashSet<>(); + public static void initialize() { + // Initialize skill categories + GameData.getAvatarDataMap().forEach((avatarId, avatarData) -> { + switch(avatarData.getWeaponType()) { + case "WEAPON_BOW": + BowAvatars.add(avatarId); + break; + case "WEAPON_CLAYMORE": + ClaymoreAvatars.add(avatarId); + break; + case "WEAPON_CATALYST": + CatalystAvatars.add(avatarId); + break; + case "WEAPON_POLE": + PolearmAvatars.add(avatarId); + break; + case "WEAPON_SWORD_ONE_HAND": + SwordAvatars.add(avatarId); + break; + } + // TODO: Initialize foods etc. + }); + } public StaminaManager(Player player) { this.player = player; @@ -244,8 +237,8 @@ public class StaminaManager { return Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.2 || Math.abs(diffZ) > 0.3; } - public int updateStaminaRelative(GameSession session, Consumption consumption) { - int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + public int updateStaminaRelative(GameSession session, Consumption consumption, PlayerProperty staminaType) { + int currentStamina = player.getProperty(staminaType); if (consumption.amount == 0) { return currentStamina; } @@ -253,7 +246,7 @@ public class StaminaManager { for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.type.toString(), consumption); if ((overriddenConsumption.type != consumption.type) && (overriddenConsumption.amount != consumption.amount)) { - logger.debug("[StaminaManager] Stamina update relative(" + + logger.debug("Stamina update relative(" + consumption.type.toString() + ", " + consumption.amount + ") overridden to relative(" + consumption.type.toString() + ", " + consumption.amount + ") by: " + listener.getKey()); return currentStamina; @@ -269,16 +262,16 @@ public class StaminaManager { } else if (newStamina > playerMaxStamina) { newStamina = playerMaxStamina; } - return setStamina(session, consumption.type.toString(), newStamina); + return setStamina(session, consumption.type.toString(), newStamina, staminaType); } - public int updateStaminaAbsolute(GameSession session, String reason, int newStamina) { + public int updateStaminaAbsolute(GameSession session, String reason, int newStamina, PlayerProperty staminaType) { int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); // notify will update for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { int overriddenNewStamina = listener.getValue().onBeforeUpdateStamina(reason, newStamina); if (overriddenNewStamina != newStamina) { - logger.debug("[StaminaManager] Stamina update absolute(" + + logger.debug("Stamina update absolute(" + reason + ", " + newStamina + ") overridden to absolute(" + reason + ", " + newStamina + ") by: " + listener.getKey()); return currentStamina; @@ -290,18 +283,22 @@ public class StaminaManager { } else if (newStamina > playerMaxStamina) { newStamina = playerMaxStamina; } - return setStamina(session, reason, newStamina); + return setStamina(session, reason, newStamina, staminaType); } // Returns new stamina and sends PlayerPropNotify - public int setStamina(GameSession session, String reason, int newStamina) { + public int setStamina(GameSession session, String reason, int newStamina, PlayerProperty staminaType) { if (!GAME_OPTIONS.staminaUsage) { newStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); } - // set stamina - player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); - session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); + player.setProperty(staminaType, newStamina); + if (staminaType == PlayerProperty.PROP_CUR_TEMPORARY_STAMINA) { + // TODO: Implement + // session.send(new PacketVehicleStaminaNotify(vehicleEntity, newStamina)); + } else { + session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); + } // notify updated for (Map.Entry listener : afterUpdateStaminaListeners.entrySet()) { listener.getValue().onAfterUpdateStamina(reason, newStamina); @@ -343,22 +340,23 @@ public class StaminaManager { // External trigger handler public void handleEvtDoSkillSuccNotify(GameSession session, int skillId, int casterId) { - // Ignore if skill not cast by not current active + // Ignore if skill not cast by not current active avatar if (casterId != player.getTeamManager().getCurrentAvatarEntity().getId()) { return; } setSkillCast(skillId, casterId); // Handle immediate stamina cost - if (ClaymoreSkills.contains(skillId)) { + int currentAvatarId = player.getTeamManager().getCurrentAvatarEntity().getAvatar().getAvatarId(); + if (ClaymoreAvatars.contains(currentAvatarId)) { // Exclude claymore as their stamina cost starts when MixinStaminaCost gets in return; } // TODO: Differentiate normal attacks from charged attacks and exclude // TODO: Temporary: Exclude non-claymore attacks for now - if (BowSkills.contains(skillId) - || SwordSkills.contains(skillId) - || PolearmSkills.contains(skillId) - || CatalystSkills.contains(skillId) + if (BowAvatars.contains(currentAvatarId) + || SwordAvatars.contains(currentAvatarId) + || PolearmAvatars.contains(currentAvatarId) + || CatalystAvatars.contains(currentAvatarId) ) { return; } @@ -367,7 +365,7 @@ public class StaminaManager { public void handleMixinCostStamina(boolean isSwim) { // Talent moving and claymore avatar charged attack duration - // logger.trace("abilityMixinCostStamina: isSwim: " + isSwim); + // logger.trace("abilityMixinCostStamina: isSwim: " + isSwim + "\tlastSkill: " + lastSkillId); if (lastSkillCasterId == player.getTeamManager().getCurrentAvatarEntity().getId()) { handleImmediateStamina(cachedSession, lastSkillId); } @@ -401,22 +399,22 @@ public class StaminaManager { switch (motionState) { case MOTION_CLIMB: if (currentState != MotionState.MOTION_CLIMB) { - updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START)); + updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START), PlayerProperty.PROP_CUR_PERSIST_STAMINA); } break; case MOTION_DASH_BEFORE_SHAKE: if (previousState != MotionState.MOTION_DASH_BEFORE_SHAKE) { - updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT)); + updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT), PlayerProperty.PROP_CUR_PERSIST_STAMINA); } break; case MOTION_CLIMB_JUMP: if (previousState != MotionState.MOTION_CLIMB_JUMP) { - updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP)); + updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP), PlayerProperty.PROP_CUR_PERSIST_STAMINA); } break; case MOTION_SWIM_DASH: if (previousState != MotionState.MOTION_SWIM_DASH) { - updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START)); + updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START), PlayerProperty.PROP_CUR_PERSIST_STAMINA); } break; } @@ -424,7 +422,7 @@ public class StaminaManager { private void handleImmediateStamina(GameSession session, int skillId) { Consumption consumption = getFightConsumption(skillId); - updateStaminaRelative(session, consumption); + updateStaminaRelative(session, consumption, PlayerProperty.PROP_CUR_PERSIST_STAMINA); } private class SustainedStaminaHandler extends TimerTask { @@ -449,41 +447,45 @@ public class StaminaManager { consumption = getSkiffConsumption(); } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { consumption = new Consumption(ConsumptionType.STANDBY); - } else if (MotionStatesCategorized.get("SWIM").contains((currentState))) { + } else if (MotionStatesCategorized.get("SWIM").contains(currentState)) { consumption = getSwimConsumptions(); - } else if (MotionStatesCategorized.get("WALK").contains((currentState))) { + } else if (MotionStatesCategorized.get("WALK").contains(currentState)) { consumption = new Consumption(ConsumptionType.WALK); - } else if (MotionStatesCategorized.get("OTHER").contains((currentState))) { + } else if (MotionStatesCategorized.get("NOCOST_NORECOVER").contains(currentState)) { + consumption = new Consumption(); + } else if (MotionStatesCategorized.get("OTHER").contains(currentState)) { consumption = getOtherConsumptions(); - } else { - // ignore + } else { // ignore return; } + if (consumption.amount < 0) { /* Do not apply reduction factor when recovering stamina TODO: Reductions that apply to all motion types: - Elemental Resonance - Wind: -15% Skills Diona E: -10% while shield lasts - applies to SP+MP Barbara E: -12% while lasts - applies to SP+MP */ + // Elemental Resonance - Winds -15% + if (player.getTeamManager().getTeamResonances().contains(10301)) { + consumption.amount *= 0.85f; + } } - // Delay 2 seconds before starts recovering stamina - if (cachedSession != null) { + // Delay 1 seconds before starts recovering stamina + if (consumption.amount != 0 && cachedSession != null) { if (consumption.amount < 0) { staminaRecoverDelay = 0; } if (consumption.amount > 0 && consumption.type != ConsumptionType.POWERED_FLY) { // For POWERED_FLY recover immediately - things like Amber's gliding exam may require this. - if (staminaRecoverDelay < 10) { - // For others recover after 2 seconds (10 ticks) - as official server does. + if (staminaRecoverDelay < 5) { + // For others recover after 1 seconds (5 ticks) - as official server does. staminaRecoverDelay++; consumption.amount = 0; - logger.trace("[StaminaManager] Delaying recovery: " + staminaRecoverDelay); + logger.trace("Delaying recovery: " + staminaRecoverDelay); } } - updateStaminaRelative(cachedSession, consumption); + updateStaminaRelative(cachedSession, consumption, PlayerProperty.PROP_CUR_PERSIST_STAMINA); } } previousState = currentState; @@ -496,6 +498,7 @@ public class StaminaManager { } private void handleDrowning() { + // TODO: fix drowning waverider entity int stamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); if (stamina < 10) { logger.trace(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + @@ -517,23 +520,24 @@ public class StaminaManager { return getTalentMovingSustainedCost(skillCasting); } // Bow avatar charged attack - if (BowSkills.contains(skillCasting)) { + int currentAvatarId = player.getTeamManager().getCurrentAvatarEntity().getAvatar().getAvatarId(); + if (BowAvatars.contains(currentAvatarId)) { return getBowSustainedCost(skillCasting); } // Claymore avatar charged attack - if (ClaymoreSkills.contains(skillCasting)) { + if (ClaymoreAvatars.contains(currentAvatarId)) { return getClaymoreSustainedCost(skillCasting); } // Catalyst avatar charged attack - if (CatalystSkills.contains(skillCasting)) { + if (CatalystAvatars.contains(currentAvatarId)) { return getCatalystSustainedCost(skillCasting); } // Polearm avatar charged attack - if (PolearmSkills.contains(skillCasting)) { + if (PolearmAvatars.contains(currentAvatarId)) { return getPolearmSustainedCost(skillCasting); } // Sword avatar charged attack - if (SwordSkills.contains(skillCasting)) { + if (SwordAvatars.contains(skillCasting)) { return getSwordSustainedCost(skillCasting); } return new Consumption(); @@ -546,18 +550,8 @@ public class StaminaManager { consumption.amount = ConsumptionType.CLIMBING.amount; } // Climbing specific reductions - // TODO: create a food cost reduction map - HashMap foodReductionMap = new HashMap<>() {{ - // TODO: get real talent id - put(0, 0.8f); // Sample food - }}; - consumption.amount *= getFoodCostReductionFactor(foodReductionMap); - - HashMap talentReductionMap = new HashMap<>() {{ - // TODO: get real talent id - put(0, 0.8f); // Xiao - }}; - consumption.amount *= getTalentCostReductionFactor(talentReductionMap); + consumption.amount *= getFoodCostReductionFactor(ClimbFoodReductionMap); + consumption.amount *= getTalentCostReductionFactor(ClimbTalentReductionMap); return consumption; } @@ -572,13 +566,9 @@ public class StaminaManager { consumption.type = ConsumptionType.SWIM_DASH; consumption.amount = ConsumptionType.SWIM_DASH.amount; } - // Reductions - HashMap talentReductionMap = new HashMap<>() {{ - // TODO: get real talent id - put(0, 0.8f); // Beidou - put(1, 0.8f); // Sangonomiya Kokomi - }}; - consumption.amount *= getTalentCostReductionFactor(talentReductionMap); + // Swimming specific reductions + consumption.amount *= getFoodCostReductionFactor(SwimFoodReductionMap); + consumption.amount *= getTalentCostReductionFactor(SwimTalentReductionMap); return consumption; } @@ -587,8 +577,8 @@ public class StaminaManager { if (currentState == MotionState.MOTION_DASH) { consumption.type = ConsumptionType.DASH; consumption.amount = ConsumptionType.DASH.amount; - // TODO: Dashing specific reductions - // Foods: + // Dashing specific reductions + consumption.amount *= getFoodCostReductionFactor(DashFoodReductionMap); } return consumption; } @@ -599,13 +589,9 @@ public class StaminaManager { return new Consumption(ConsumptionType.POWERED_FLY); } Consumption consumption = new Consumption(ConsumptionType.FLY); - // Passive Talents - HashMap talentReductionMap = new HashMap<>() {{ - put(212301, 0.8f); // Amber - put(222301, 0.8f); // Venti - }}; - consumption.amount *= getTalentCostReductionFactor(talentReductionMap); - // TODO: Foods + // Flying specific reductions + consumption.amount *= getFoodCostReductionFactor(FlyFoodReductionMap); + consumption.amount *= getTalentCostReductionFactor(FlyTalentReductionMap); return consumption; } @@ -619,12 +605,17 @@ public class StaminaManager { } private Consumption getOtherConsumptions() { - if (currentState == MotionState.MOTION_NOTIFY) { - if (BowSkills.contains(lastSkillId)) { + switch (currentState) { + case MOTION_NOTIFY: +// if (BowSkills.contains(lastSkillId)) { +// return new Consumption(ConsumptionType.FIGHT, 500); +// } + break; + case MOTION_FIGHT: + // TODO: what if charged attack return new Consumption(ConsumptionType.FIGHT, 500); - } } - // TODO: Add other logic + return new Consumption(); } @@ -685,11 +676,13 @@ public class StaminaManager { // Character specific handling switch (skillId) { case 10571: // Arataki Itto, does not consume stamina at all. + case 10532: // Sayu, windwheel does not consume stamina. consumption.amount = 0; break; case 10160: // Diluc, with talent "Relentless" stamina cost is decreased by 50% - // TODO: How to get talent status? - consumption.amount /= 2; + if (player.getTeamManager().getCurrentAvatarEntity().getAvatar().getProudSkillList().contains(162101)) { + consumption.amount /= 2; + } break; } return consumption; diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketVehicleStaminaNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketVehicleStaminaNotify.java new file mode 100644 index 000000000..917bce387 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketVehicleStaminaNotify.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.VehicleStaminaNotifyOuterClass.VehicleStaminaNotify; + +public class PacketVehicleStaminaNotify extends BasePacket { + + public PacketVehicleStaminaNotify(GameEntity entity, int newStamina) { + super(PacketOpcodes.VehicleStaminaNotify); + VehicleStaminaNotify.Builder proto = VehicleStaminaNotify.newBuilder(); + + proto.setEntityId(entity.getId()); + proto.setCurStamina(newStamina); + + this.setData(proto.build()); + } +} From 00a248725edbcb12f0b1cc5cf5fc6a451e7d02dc Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Wed, 11 May 2022 17:11:57 -0700 Subject: [PATCH 2/8] Feature: vehicle stamina 1. Remove references. 2. Handle vehicle stamina. --- .../AfterUpdateStaminaListener.java | 2 +- .../BeforeUpdateStaminaListener.java | 4 +- .../StaminaManager/ConsumptionType.java | 9 +- .../StaminaManager/StaminaManager.java | 209 ++++++++++-------- .../emu/grasscutter/game/player/Player.java | 2 +- .../recv/HandlerVehicleInteractReq.java | 1 + .../send/PacketVehicleStaminaNotify.java | 5 +- 7 files changed, 127 insertions(+), 105 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java index bb4f0b188..11a5c9178 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/AfterUpdateStaminaListener.java @@ -8,5 +8,5 @@ public interface AfterUpdateStaminaListener { * @param reason Why updating stamina. * @param newStamina New Stamina value. */ - void onAfterUpdateStamina(String reason, int newStamina); + void onAfterUpdateStamina(String reason, int newStamina, boolean isCharacterStamina); } diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java index 02f1f3522..39075f35b 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/BeforeUpdateStaminaListener.java @@ -8,7 +8,7 @@ public interface BeforeUpdateStaminaListener { * @param newStamina New ABSOLUTE stamina value. * @return true if you want to cancel this update, otherwise false. */ - int onBeforeUpdateStamina(String reason, int newStamina); + int onBeforeUpdateStamina(String reason, int newStamina, boolean isCharacterStamina); /** * onBeforeUpdateStamina() will be called before StaminaManager attempt to update the player's current stamina. * This gives listeners a chance to intercept this update. @@ -16,5 +16,5 @@ public interface BeforeUpdateStaminaListener { * @param consumption ConsumptionType and RELATIVE stamina change amount. * @return true if you want to cancel this update, otherwise false. */ - Consumption onBeforeUpdateStamina(String reason, Consumption consumption); + Consumption onBeforeUpdateStamina(String reason, Consumption consumption, boolean isCharacterStamina); } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java index feb42d14e..506bf1728 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/ConsumptionType.java @@ -13,18 +13,19 @@ public enum ConsumptionType { // Slow swimming is handled per movement, not per second. // Arm movement frequency depends on gender/age/height. // TODO: Instead of cost -80 per tick, find a proper way to calculate cost. - SKIFF(-300), // TODO: Get real value + SKIFF_DASH(-204), SPRINT(-1800), - SWIM_DASH_START(-20), + SWIM_DASH_START(-2000), SWIM_DASH(-204), // -10.2 per second, 5Hz = -204 each tick SWIMMING(-80), TALENT_DASH(-300), // -1500 per second, 5Hz = -300 each tick TALENT_DASH_START(-1000), // restore - POWERED_FLY(500), // TODO: Get real value - POWERED_SKIFF(2000), // TODO: Get real value + POWERED_FLY(500), + POWERED_SKIFF(500), RUN(500), + SKIFF(500), STANDBY(500), WALK(500); diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index e076f5856..85225b4b8 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -14,6 +14,7 @@ import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo; import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState; import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType; import emu.grasscutter.net.proto.VectorOuterClass.Vector; +import emu.grasscutter.net.proto.VehicleInteractTypeOuterClass.VehicleInteractType; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.*; import emu.grasscutter.utils.Position; @@ -48,7 +49,7 @@ public class StaminaManager { ))); put("SKIFF", new HashSet<>(List.of( MotionState.MOTION_SKIFF_BOARDING, // NOT OBSERVED even when boarding - MotionState.MOTION_SKIFF_DASH, // NOT OBSERVED even when dashing + MotionState.MOTION_SKIFF_DASH, // sustained, observed with waverider entity ID. MotionState.MOTION_SKIFF_NORMAL, // sustained, OBSERVED when both normal and dashing MotionState.MOTION_SKIFF_POWERED_DASH // sustained, recover ))); @@ -108,7 +109,8 @@ public class StaminaManager { }}; private final Logger logger = Grasscutter.getLogger(); - public final static int GlobalMaximumStamina = 24000; + public final static int GlobalCharacterMaximumStamina = 24000; + public final static int GlobalVehicleMaxStamina = 24000; private Position currentCoordinates = new Position(0, 0, 0); private Position previousCoordinates = new Position(0, 0, 0); private MotionState currentState = MotionState.MOTION_STANDBY; @@ -122,9 +124,10 @@ public class StaminaManager { private int lastSkillId = 0; private int lastSkillCasterId = 0; private boolean lastSkillFirstTick = true; + private int vehicleId = -1; + private int vehicleStamina = GlobalVehicleMaxStamina; private static final HashSet TalentMovements = new HashSet<>(List.of( - 10013, // Kamisato Ayaka - 10413 // Mona + 10013, 10413 )); private static final HashMap ClimbFoodReductionMap = new HashMap<>() {{ // TODO: get real food id @@ -143,15 +146,15 @@ public class StaminaManager { put(0, 0.8f); // Sample food }}; private static final HashMap ClimbTalentReductionMap = new HashMap<>() {{ - put(262301, 0.8f); // Xiao + put(262301, 0.8f); }}; private static final HashMap FlyTalentReductionMap = new HashMap<>() {{ - put(212301, 0.8f); // Amber - put(222301, 0.8f); // Venti + put(212301, 0.8f); + put(222301, 0.8f); }}; private static final HashMap SwimTalentReductionMap = new HashMap<>() {{ - put(242301, 0.8f); // Beidou - put(542301, 0.8f); // Sangonomiya Kokomi + put(242301, 0.8f); + put(542301, 0.8f); }}; public static final HashSet BowAvatars = new HashSet<>(); @@ -163,25 +166,15 @@ public class StaminaManager { public static void initialize() { // Initialize skill categories GameData.getAvatarDataMap().forEach((avatarId, avatarData) -> { - switch(avatarData.getWeaponType()) { - case "WEAPON_BOW": - BowAvatars.add(avatarId); - break; - case "WEAPON_CLAYMORE": - ClaymoreAvatars.add(avatarId); - break; - case "WEAPON_CATALYST": - CatalystAvatars.add(avatarId); - break; - case "WEAPON_POLE": - PolearmAvatars.add(avatarId); - break; - case "WEAPON_SWORD_ONE_HAND": - SwordAvatars.add(avatarId); - break; + switch (avatarData.getWeaponType()) { + case "WEAPON_BOW" -> BowAvatars.add(avatarId); + case "WEAPON_CLAYMORE" -> ClaymoreAvatars.add(avatarId); + case "WEAPON_CATALYST" -> CatalystAvatars.add(avatarId); + case "WEAPON_POLE" -> PolearmAvatars.add(avatarId); + case "WEAPON_SWORD_ONE_HAND" -> SwordAvatars.add(avatarId); } - // TODO: Initialize foods etc. }); + // TODO: Initialize foods etc. } public StaminaManager(Player player) { @@ -196,6 +189,22 @@ public class StaminaManager { lastSkillCasterId = skillCasterId; } + public int getMaxCharacterStamina() { + return player.getProperty(PlayerProperty.PROP_MAX_STAMINA); + } + + public int getCurrentCharacterStamina() { + return player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + } + + public int getMaxVehicleStamina() { + return GlobalVehicleMaxStamina; + } + + public int getCurrentVehicleStamina() { + return vehicleStamina; + } + public boolean registerBeforeUpdateStaminaListener(String listenerName, BeforeUpdateStaminaListener listener) { if (beforeUpdateStaminaListeners.containsKey(listenerName)) { return false; @@ -237,14 +246,14 @@ public class StaminaManager { return Math.abs(diffX) > 0.3 || Math.abs(diffY) > 0.2 || Math.abs(diffZ) > 0.3; } - public int updateStaminaRelative(GameSession session, Consumption consumption, PlayerProperty staminaType) { - int currentStamina = player.getProperty(staminaType); + public int updateStaminaRelative(GameSession session, Consumption consumption, boolean isCharacterStamina) { + int currentStamina = isCharacterStamina ? getCurrentCharacterStamina() : getCurrentVehicleStamina(); if (consumption.amount == 0) { return currentStamina; } // notify will update for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { - Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.type.toString(), consumption); + Consumption overriddenConsumption = listener.getValue().onBeforeUpdateStamina(consumption.type.toString(), consumption, isCharacterStamina); if ((overriddenConsumption.type != consumption.type) && (overriddenConsumption.amount != consumption.amount)) { logger.debug("Stamina update relative(" + consumption.type.toString() + ", " + consumption.amount + ") overridden to relative(" + @@ -252,24 +261,24 @@ public class StaminaManager { return currentStamina; } } - int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); - logger.trace(currentStamina + "/" + playerMaxStamina + "\t" + currentState + "\t" + + int maxStamina = isCharacterStamina ? getMaxCharacterStamina() : getMaxVehicleStamina(); + logger.warn((isCharacterStamina ? "C " : "V ") + currentStamina + "/" + maxStamina + "\t" + currentState + "\t" + (isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.type + "," + consumption.amount + ")"); int newStamina = currentStamina + consumption.amount; if (newStamina < 0) { newStamina = 0; - } else if (newStamina > playerMaxStamina) { - newStamina = playerMaxStamina; + } else if (newStamina > maxStamina) { + newStamina = maxStamina; } - return setStamina(session, consumption.type.toString(), newStamina, staminaType); + return setStamina(session, consumption.type.toString(), newStamina, isCharacterStamina); } - public int updateStaminaAbsolute(GameSession session, String reason, int newStamina, PlayerProperty staminaType) { - int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + public int updateStaminaAbsolute(GameSession session, String reason, int newStamina, boolean isCharacterStamina) { + int currentStamina = isCharacterStamina ? getCurrentCharacterStamina() : getCurrentVehicleStamina(); // notify will update for (Map.Entry listener : beforeUpdateStaminaListeners.entrySet()) { - int overriddenNewStamina = listener.getValue().onBeforeUpdateStamina(reason, newStamina); + int overriddenNewStamina = listener.getValue().onBeforeUpdateStamina(reason, newStamina, isCharacterStamina); if (overriddenNewStamina != newStamina) { logger.debug("Stamina update absolute(" + reason + ", " + newStamina + ") overridden to absolute(" + @@ -277,31 +286,31 @@ public class StaminaManager { return currentStamina; } } - int playerMaxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); + int maxStamina = isCharacterStamina ? getMaxCharacterStamina() : getMaxVehicleStamina(); if (newStamina < 0) { newStamina = 0; - } else if (newStamina > playerMaxStamina) { - newStamina = playerMaxStamina; + } else if (newStamina > maxStamina) { + newStamina = maxStamina; } - return setStamina(session, reason, newStamina, staminaType); + return setStamina(session, reason, newStamina, isCharacterStamina); } - // Returns new stamina and sends PlayerPropNotify - public int setStamina(GameSession session, String reason, int newStamina, PlayerProperty staminaType) { + // Returns new stamina and sends PlayerPropNotify or VehicleStaminaNotify + public int setStamina(GameSession session, String reason, int newStamina, boolean isCharacterStamina) { if (!GAME_OPTIONS.staminaUsage) { - newStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); + newStamina = getMaxCharacterStamina(); } - // set stamina - player.setProperty(staminaType, newStamina); - if (staminaType == PlayerProperty.PROP_CUR_TEMPORARY_STAMINA) { - // TODO: Implement - // session.send(new PacketVehicleStaminaNotify(vehicleEntity, newStamina)); - } else { + // set stamina if is character stamina + if (isCharacterStamina) { + player.setProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA, newStamina); session.send(new PacketPlayerPropNotify(player, PlayerProperty.PROP_CUR_PERSIST_STAMINA)); + } else { + vehicleStamina = newStamina; + session.send(new PacketVehicleStaminaNotify(vehicleId, ((float) newStamina) / 100)); } // notify updated for (Map.Entry listener : afterUpdateStaminaListeners.entrySet()) { - listener.getValue().onAfterUpdateStamina(reason, newStamina); + listener.getValue().onAfterUpdateStamina(reason, newStamina, isCharacterStamina); } return newStamina; } @@ -379,11 +388,11 @@ public class StaminaManager { MotionState motionState = motionInfo.getState(); int notifyEntityId = entity.getId(); int currentAvatarEntityId = session.getPlayer().getTeamManager().getCurrentAvatarEntity().getId(); - if (notifyEntityId != currentAvatarEntityId) { + if (notifyEntityId != currentAvatarEntityId && notifyEntityId != vehicleId) { return; } currentState = motionState; - // logger.trace("" + currentState); + // logger.trace(currentState + "\t" + (notifyEntityId == currentAvatarEntityId ? "character" : "vehicle")); Vector posVector = motionInfo.getPos(); Position newPos = new Position(posVector.getX(), posVector.getY(), posVector.getZ()); if (newPos.getX() != 0 && newPos.getY() != 0 && newPos.getZ() != 0) { @@ -393,28 +402,40 @@ public class StaminaManager { handleImmediateStamina(session, motionState); } + public void handleVehicleInteractReq(GameSession session, int vehicleId, VehicleInteractType vehicleInteractType) { + if (vehicleInteractType == VehicleInteractType.VEHICLE_INTERACT_IN) { + this.vehicleId = vehicleId; + // Reset character stamina here to prevent falling into water immediately on ejection if char stamina is + // close to empty when boarding. + updateStaminaAbsolute(session, "board vehicle", getMaxCharacterStamina(), true); + updateStaminaAbsolute(session, "board vehicle", getMaxVehicleStamina(), false); + } else { + this.vehicleId = -1; + } + } + // Internal handler private void handleImmediateStamina(GameSession session, @NotNull MotionState motionState) { switch (motionState) { case MOTION_CLIMB: if (currentState != MotionState.MOTION_CLIMB) { - updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START), PlayerProperty.PROP_CUR_PERSIST_STAMINA); + updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_START), true); } break; case MOTION_DASH_BEFORE_SHAKE: if (previousState != MotionState.MOTION_DASH_BEFORE_SHAKE) { - updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT), PlayerProperty.PROP_CUR_PERSIST_STAMINA); + updateStaminaRelative(session, new Consumption(ConsumptionType.SPRINT), true); } break; case MOTION_CLIMB_JUMP: if (previousState != MotionState.MOTION_CLIMB_JUMP) { - updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP), PlayerProperty.PROP_CUR_PERSIST_STAMINA); + updateStaminaRelative(session, new Consumption(ConsumptionType.CLIMB_JUMP), true); } break; case MOTION_SWIM_DASH: if (previousState != MotionState.MOTION_SWIM_DASH) { - updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START), PlayerProperty.PROP_CUR_PERSIST_STAMINA); + updateStaminaRelative(session, new Consumption(ConsumptionType.SWIM_DASH_START), true); } break; } @@ -422,18 +443,20 @@ public class StaminaManager { private void handleImmediateStamina(GameSession session, int skillId) { Consumption consumption = getFightConsumption(skillId); - updateStaminaRelative(session, consumption, PlayerProperty.PROP_CUR_PERSIST_STAMINA); + updateStaminaRelative(session, consumption, true); } private class SustainedStaminaHandler extends TimerTask { public void run() { boolean moving = isPlayerMoving(); - int currentStamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); - int maxStamina = player.getProperty(PlayerProperty.PROP_MAX_STAMINA); - if (moving || (currentStamina < maxStamina)) { + int currentCharacterStamina = getCurrentCharacterStamina(); + int maxCharacterStamina = getMaxCharacterStamina(); + int currentVehicleStamina = getCurrentVehicleStamina(); + int maxVehicleStamina = getMaxVehicleStamina(); + if (moving || (currentCharacterStamina < maxCharacterStamina) || (currentVehicleStamina < maxVehicleStamina)) { logger.trace("Player moving: " + moving + ", stamina full: " + - (currentStamina >= maxStamina) + ", recalculate stamina"); - + (currentCharacterStamina >= maxCharacterStamina) + ", recalculate stamina"); + boolean isCharacterStamina = true; Consumption consumption; if (MotionStatesCategorized.get("CLIMB").contains(currentState)) { consumption = getClimbConsumption(); @@ -445,6 +468,7 @@ public class StaminaManager { consumption = new Consumption(ConsumptionType.RUN); } else if (MotionStatesCategorized.get("SKIFF").contains(currentState)) { consumption = getSkiffConsumption(); + isCharacterStamina = false; } else if (MotionStatesCategorized.get("STANDBY").contains(currentState)) { consumption = new Consumption(ConsumptionType.STANDBY); } else if (MotionStatesCategorized.get("SWIM").contains(currentState)) { @@ -459,16 +483,10 @@ public class StaminaManager { return; } - if (consumption.amount < 0) { - /* Do not apply reduction factor when recovering stamina - TODO: Reductions that apply to all motion types: - Skills - Diona E: -10% while shield lasts - applies to SP+MP - Barbara E: -12% while lasts - applies to SP+MP - */ - // Elemental Resonance - Winds -15% + if (consumption.amount < 0 && isCharacterStamina) { + // Do not apply reduction factor when recovering stamina if (player.getTeamManager().getTeamResonances().contains(10301)) { - consumption.amount *= 0.85f; + consumption.amount *= 0.85f; } } // Delay 1 seconds before starts recovering stamina @@ -476,8 +494,10 @@ public class StaminaManager { if (consumption.amount < 0) { staminaRecoverDelay = 0; } - if (consumption.amount > 0 && consumption.type != ConsumptionType.POWERED_FLY) { - // For POWERED_FLY recover immediately - things like Amber's gliding exam may require this. + if (consumption.amount > 0 + && consumption.type != ConsumptionType.POWERED_FLY + && consumption.type != ConsumptionType.POWERED_SKIFF) { + // For POWERED_* recover immediately - things like Amber's gliding exam and skiff challenges may require this. if (staminaRecoverDelay < 5) { // For others recover after 1 seconds (5 ticks) - as official server does. staminaRecoverDelay++; @@ -485,7 +505,7 @@ public class StaminaManager { logger.trace("Delaying recovery: " + staminaRecoverDelay); } } - updateStaminaRelative(cachedSession, consumption, PlayerProperty.PROP_CUR_PERSIST_STAMINA); + updateStaminaRelative(cachedSession, consumption, isCharacterStamina); } } previousState = currentState; @@ -499,10 +519,10 @@ public class StaminaManager { private void handleDrowning() { // TODO: fix drowning waverider entity - int stamina = player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA); + int stamina = getCurrentCharacterStamina(); if (stamina < 10) { - logger.trace(player.getProperty(PlayerProperty.PROP_CUR_PERSIST_STAMINA) + "/" + - player.getProperty(PlayerProperty.PROP_MAX_STAMINA) + "\t" + currentState); + logger.trace(getCurrentCharacterStamina() + "/" + + getMaxCharacterStamina() + "\t" + currentState); if (currentState != MotionState.MOTION_SWIM_IDLE) { killAvatar(cachedSession, cachedEntity, PlayerDieType.PLAYER_DIE_DRAWN); } @@ -530,15 +550,15 @@ public class StaminaManager { } // Catalyst avatar charged attack if (CatalystAvatars.contains(currentAvatarId)) { - return getCatalystSustainedCost(skillCasting); + return getCatalystCost(skillCasting); } // Polearm avatar charged attack if (PolearmAvatars.contains(currentAvatarId)) { - return getPolearmSustainedCost(skillCasting); + return getPolearmCost(skillCasting); } // Sword avatar charged attack if (SwordAvatars.contains(skillCasting)) { - return getSwordSustainedCost(skillCasting); + return getSwordCost(skillCasting); } return new Consumption(); } @@ -596,12 +616,13 @@ public class StaminaManager { } private Consumption getSkiffConsumption() { - // POWERED_SKIFF, e.g. wind tunnel - if (currentState == MotionState.MOTION_SKIFF_POWERED_DASH) { - return new Consumption(ConsumptionType.POWERED_SKIFF); - } // No known reduction for skiffing. - return new Consumption(ConsumptionType.SKIFF); + return switch (currentState) { + case MOTION_SKIFF_DASH -> new Consumption(ConsumptionType.SKIFF_DASH); + case MOTION_SKIFF_POWERED_DASH -> new Consumption(ConsumptionType.POWERED_SKIFF); + case MOTION_SKIFF_NORMAL -> new Consumption(ConsumptionType.SKIFF); + default -> new Consumption(); + }; } private Consumption getOtherConsumptions() { @@ -662,11 +683,11 @@ public class StaminaManager { return new Consumption(ConsumptionType.FIGHT, +500); } - private Consumption getCatalystSustainedCost(int skillId) { + private Consumption getCatalystCost(int skillId) { Consumption consumption = new Consumption(ConsumptionType.FIGHT, -5000); // Character specific handling switch (skillId) { - // TODO: Yanfei + // TODO: } return consumption; } @@ -675,11 +696,11 @@ public class StaminaManager { Consumption consumption = new Consumption(ConsumptionType.FIGHT, -1333); // 4000 / 3 = 1333 // Character specific handling switch (skillId) { - case 10571: // Arataki Itto, does not consume stamina at all. - case 10532: // Sayu, windwheel does not consume stamina. + case 10571: + case 10532: consumption.amount = 0; break; - case 10160: // Diluc, with talent "Relentless" stamina cost is decreased by 50% + case 10160: if (player.getTeamManager().getCurrentAvatarEntity().getAvatar().getProudSkillList().contains(162101)) { consumption.amount /= 2; } @@ -688,7 +709,7 @@ public class StaminaManager { return consumption; } - private Consumption getPolearmSustainedCost(int skillId) { + private Consumption getPolearmCost(int skillId) { Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2500); // Character specific handling switch (skillId) { @@ -697,11 +718,11 @@ public class StaminaManager { return consumption; } - private Consumption getSwordSustainedCost(int skillId) { + private Consumption getSwordCost(int skillId) { Consumption consumption = new Consumption(ConsumptionType.FIGHT, -2000); // Character specific handling switch (skillId) { - case 10421: // Keqing, -2500 + case 10421: consumption.amount = -2500; break; } diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index fd5343be8..58e500b97 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -1237,7 +1237,7 @@ public class Player { } else if (prop == PlayerProperty.PROP_IS_TRANSFERABLE) { // 10009 if (!(0 <= value && value <= 1)) { return false; } } else if (prop == PlayerProperty.PROP_MAX_STAMINA) { // 10010 - if (!(value >= 0 && value <= StaminaManager.GlobalMaximumStamina)) { return false; } + if (!(value >= 0 && value <= StaminaManager.GlobalCharacterMaximumStamina)) { return false; } } else if (prop == PlayerProperty.PROP_CUR_PERSIST_STAMINA) { // 10011 int playerMaximumStamina = getProperty(PlayerProperty.PROP_MAX_STAMINA); if (!(value >= 0 && value <= playerMaximumStamina)) { return false; } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerVehicleInteractReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerVehicleInteractReq.java index 3baba9c5b..d45befa89 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerVehicleInteractReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerVehicleInteractReq.java @@ -14,6 +14,7 @@ public class HandlerVehicleInteractReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { VehicleInteractReqOuterClass.VehicleInteractReq req = VehicleInteractReqOuterClass.VehicleInteractReq.parseFrom(payload); + session.getPlayer().getStaminaManager().handleVehicleInteractReq(session, req.getEntityId(), req.getInteractType()); session.send(new PacketVehicleInteractRsp(session.getPlayer(), req.getEntityId(), req.getInteractType())); } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketVehicleStaminaNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketVehicleStaminaNotify.java index 917bce387..0a6a315e3 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketVehicleStaminaNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketVehicleStaminaNotify.java @@ -1,17 +1,16 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.VehicleStaminaNotifyOuterClass.VehicleStaminaNotify; public class PacketVehicleStaminaNotify extends BasePacket { - public PacketVehicleStaminaNotify(GameEntity entity, int newStamina) { + public PacketVehicleStaminaNotify(int vehicleId, float newStamina) { super(PacketOpcodes.VehicleStaminaNotify); VehicleStaminaNotify.Builder proto = VehicleStaminaNotify.newBuilder(); - proto.setEntityId(entity.getId()); + proto.setEntityId(vehicleId); proto.setCurStamina(newStamina); this.setData(proto.build()); From 53797dfd3bfba762561173e0ebabc8bed9842d30 Mon Sep 17 00:00:00 2001 From: gentlespoon Date: Wed, 11 May 2022 17:15:32 -0700 Subject: [PATCH 3/8] Fix logging level --- .../game/managers/StaminaManager/StaminaManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java index 85225b4b8..1f452a667 100644 --- a/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java +++ b/src/main/java/emu/grasscutter/game/managers/StaminaManager/StaminaManager.java @@ -262,7 +262,7 @@ public class StaminaManager { } } int maxStamina = isCharacterStamina ? getMaxCharacterStamina() : getMaxVehicleStamina(); - logger.warn((isCharacterStamina ? "C " : "V ") + currentStamina + "/" + maxStamina + "\t" + currentState + "\t" + + logger.trace((isCharacterStamina ? "C " : "V ") + currentStamina + "/" + maxStamina + "\t" + currentState + "\t" + (isPlayerMoving() ? "moving" : " ") + "\t(" + consumption.type + "," + consumption.amount + ")"); int newStamina = currentStamina + consumption.amount; From 010455548fd8b31b854cb61cfca6a69c50bd0d24 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Wed, 11 May 2022 20:33:25 +0930 Subject: [PATCH 4/8] Add Inventory.payItems() method --- .../grasscutter/game/inventory/Inventory.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/main/java/emu/grasscutter/game/inventory/Inventory.java b/src/main/java/emu/grasscutter/game/inventory/Inventory.java index 4a217ba54..2c0478dda 100644 --- a/src/main/java/emu/grasscutter/game/inventory/Inventory.java +++ b/src/main/java/emu/grasscutter/game/inventory/Inventory.java @@ -7,6 +7,7 @@ import java.util.List; import emu.grasscutter.GameConstants; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.def.AvatarCostumeData; import emu.grasscutter.data.def.AvatarData; import emu.grasscutter.data.def.AvatarFlycloakData; @@ -256,6 +257,52 @@ public class Inventory implements Iterable { getPlayer().setCrystals(player.getCrystals() + count); } } + + private int getVirtualItemCount(int itemId) { + switch (itemId) { + case 201: // Primogem + return player.getPrimogems(); + case 202: // Mora + return player.getMora(); + case 203: // Genesis Crystals + return player.getCrystals(); + default: + GameItem item = getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId); // What if we ever want to operate on weapons/relics/furniture? :S + return (item == null) ? 0 : item.getCount(); + } + } + + public boolean payItems(Collection items) { + return payItems(items, null); + } + + public synchronized boolean payItems(Collection costItems, ActionReason reason) { + // Make sure player has requisite items + for (ItemParamData cost : costItems) { + if (getVirtualItemCount(cost.getId()) < cost.getCount()) { + return false; + } + } + // All costs are satisfied, now remove them all + for (ItemParamData cost : costItems) { + switch (cost.getId()) { + case 201 -> // Primogem + player.setPrimogems(player.getPrimogems() - cost.getCount()); + case 202 -> // Mora + player.setMora(player.getMora() - cost.getCount()); + case 203 -> // Genesis Crystals + player.setCrystals(player.getCrystals() - cost.getCount()); + default -> + removeItem(getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()), cost.getCount()); + } + } + + if (reason != null) { // Do we need these? + // getPlayer().sendPacket(new PacketItemAddHintNotify(changedItems, reason)); + } + // getPlayer().sendPacket(new PacketStoreItemChangeNotify(changedItems)); + return true; + } public void removeItems(List items) { // TODO Bulk delete From c7d5d8640ccb2dadc67e560a45c405686b8fe461 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Wed, 11 May 2022 21:53:25 +0930 Subject: [PATCH 5/8] Add some usage of Inventory.payItems() And add quantity to it --- .../grasscutter/game/gacha/GachaManager.java | 10 +-- .../grasscutter/game/inventory/Inventory.java | 20 +++-- .../game/managers/InventoryManager.java | 75 +++++-------------- .../packet/recv/HandlerBuyGoodsReq.java | 35 ++------- 4 files changed, 39 insertions(+), 101 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index f0baf65a9..c730a646a 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -13,6 +13,7 @@ import com.google.gson.reflect.TypeToken; import com.sun.nio.file.SensitivityWatchEventModifier; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; +import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.def.ItemData; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.avatar.Avatar; @@ -127,13 +128,8 @@ public class GachaManager { } // Spend currency - if (banner.getCostItem() > 0) { - GameItem costItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(banner.getCostItem()); - if (costItem == null || costItem.getCount() < times) { - return; - } - - player.getInventory().removeItem(costItem, times); + if (banner.getCostItem() > 0 && !player.getInventory().payItems(new ItemParamData[] {new ItemParamData(banner.getCostItem(), times)})) { + return; } // Roll diff --git a/src/main/java/emu/grasscutter/game/inventory/Inventory.java b/src/main/java/emu/grasscutter/game/inventory/Inventory.java index 2c0478dda..6936ca066 100644 --- a/src/main/java/emu/grasscutter/game/inventory/Inventory.java +++ b/src/main/java/emu/grasscutter/game/inventory/Inventory.java @@ -272,14 +272,18 @@ public class Inventory implements Iterable { } } - public boolean payItems(Collection items) { - return payItems(items, null); + public boolean payItems(ItemParamData[] costItems) { + return payItems(costItems, 1, null); + } + + public boolean payItems(ItemParamData[] costItems, int quantity) { + return payItems(costItems, quantity, null); } - public synchronized boolean payItems(Collection costItems, ActionReason reason) { + public synchronized boolean payItems(ItemParamData[] costItems, int quantity, ActionReason reason) { // Make sure player has requisite items for (ItemParamData cost : costItems) { - if (getVirtualItemCount(cost.getId()) < cost.getCount()) { + if (getVirtualItemCount(cost.getId()) < (cost.getCount() * quantity)) { return false; } } @@ -287,13 +291,13 @@ public class Inventory implements Iterable { for (ItemParamData cost : costItems) { switch (cost.getId()) { case 201 -> // Primogem - player.setPrimogems(player.getPrimogems() - cost.getCount()); + player.setPrimogems(player.getPrimogems() - (cost.getCount() * quantity)); case 202 -> // Mora - player.setMora(player.getMora() - cost.getCount()); + player.setMora(player.getMora() - (cost.getCount() * quantity)); case 203 -> // Genesis Crystals - player.setCrystals(player.getCrystals() - cost.getCount()); + player.setCrystals(player.getCrystals() - (cost.getCount() * quantity)); default -> - removeItem(getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()), cost.getCount()); + removeItem(getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()), cost.getCount() * quantity); } } diff --git a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java index f3b9c0293..f75f8b074 100644 --- a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java +++ b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java @@ -1,6 +1,7 @@ package emu.grasscutter.game.managers; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -496,27 +497,16 @@ public class InventoryManager { return; } - // Make sure player has promote items - for (ItemParamData cost : nextPromoteData.getCostItems()) { - GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); - if (feedItem == null || feedItem.getCount() < cost.getCount()) { - return; - } + // Pay materials and mora if possible + ItemParamData[] costs = nextPromoteData.getCostItems(); // Can this be null? + if (nextPromoteData.getCoinCost() > 0) { + costs = Arrays.copyOf(costs, costs.length + 1); + costs[costs.length-1] = new ItemParamData(202, nextPromoteData.getCoinCost()); } - - // Mora check - if (player.getMora() >= nextPromoteData.getCoinCost()) { - player.setMora(player.getMora() - nextPromoteData.getCoinCost()); - } else { + if (!player.getInventory().payItems(costs)) { return; } - // Consume promote filler items - for (ItemParamData cost : nextPromoteData.getCostItems()) { - GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); - player.getInventory().removeItem(feedItem, cost.getCount()); - } - int oldPromoteLevel = weapon.getPromoteLevel(); weapon.setPromoteLevel(nextPromoteLevel); weapon.save(); @@ -552,27 +542,16 @@ public class InventoryManager { return; } - // Make sure player has cost items - for (ItemParamData cost : nextPromoteData.getCostItems()) { - GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); - if (feedItem == null || feedItem.getCount() < cost.getCount()) { - return; - } + // Pay materials and mora if possible + ItemParamData[] costs = nextPromoteData.getCostItems(); // Can this be null? + if (nextPromoteData.getCoinCost() > 0) { + costs = Arrays.copyOf(costs, costs.length + 1); + costs[costs.length-1] = new ItemParamData(202, nextPromoteData.getCoinCost()); } - - // Mora check - if (player.getMora() >= nextPromoteData.getCoinCost()) { - player.setMora(player.getMora() - nextPromoteData.getCoinCost()); - } else { + if (!player.getInventory().payItems(costs)) { return; } - // Consume promote filler items - for (ItemParamData cost : nextPromoteData.getCostItems()) { - GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); - player.getInventory().removeItem(feedItem, cost.getCount()); - } - // Update promote level avatar.setPromoteLevel(nextPromoteLevel); @@ -764,33 +743,15 @@ public class InventoryManager { return; } - // Make sure player has cost items - for (ItemParamData cost : proudSkill.getCostItems()) { - if (cost.getId() == 0) { - continue; - } - GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); - if (feedItem == null || feedItem.getCount() < cost.getCount()) { - return; - } + // Pay materials and mora if possible + List costs = proudSkill.getCostItems(); // Can this be null? + if (proudSkill.getCoinCost() > 0) { + costs.add(new ItemParamData(202, proudSkill.getCoinCost())); } - - // Mora check - if (player.getMora() >= proudSkill.getCoinCost()) { - player.setMora(player.getMora() - proudSkill.getCoinCost()); - } else { + if (!player.getInventory().payItems(costs.toArray(new ItemParamData[0]))) { return; } - // Consume promote filler items - for (ItemParamData cost : proudSkill.getCostItems()) { - if (cost.getId() == 0) { - continue; - } - GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(cost.getId()); - player.getInventory().removeItem(feedItem, cost.getCount()); - } - // Upgrade skill avatar.getSkillLevelMap().put(skillId, nextLevel); avatar.save(); diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java index 0a2c30802..fc9564734 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java @@ -18,7 +18,7 @@ import emu.grasscutter.server.packet.send.PacketBuyGoodsRsp; import emu.grasscutter.server.packet.send.PacketStoreItemChangeNotify; import emu.grasscutter.utils.Utils; -import java.util.HashMap; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -56,36 +56,13 @@ public class HandlerBuyGoodsReq extends PacketHandler { return; } - if (sg.getScoin() > 0 && session.getPlayer().getMora() < buyGoodsReq.getBoughtNum() * sg.getScoin()) { + List costs = sg.getCostItemList(); // Can this even be null? + costs.add(new ItemParamData(202, sg.getScoin())); + costs.add(new ItemParamData(201, sg.getHcoin())); + costs.add(new ItemParamData(203, sg.getMcoin())); + if (!session.getPlayer().getInventory().payItems(costs.toArray(new ItemParamData[0]), buyGoodsReq.getBoughtNum())) { return; } - if (sg.getHcoin() > 0 && session.getPlayer().getPrimogems() < buyGoodsReq.getBoughtNum() * sg.getHcoin()) { - return; - } - if (sg.getMcoin() > 0 && session.getPlayer().getCrystals() < buyGoodsReq.getBoughtNum() * sg.getMcoin()) { - return; - } - - HashMap itemsCache = new HashMap<>(); - if (sg.getCostItemList() != null) { - for (ItemParamData p : sg.getCostItemList()) { - Optional invItem = session.getPlayer().getInventory().getItems().values().stream().filter(x -> x.getItemId() == p.getId()).findFirst(); - if (invItem.isEmpty() || invItem.get().getCount() < p.getCount()) - return; - itemsCache.put(invItem.get(), p.getCount() * buyGoodsReq.getBoughtNum()); - } - } - - session.getPlayer().setMora(session.getPlayer().getMora() - buyGoodsReq.getBoughtNum() * sg.getScoin()); - session.getPlayer().setPrimogems(session.getPlayer().getPrimogems() - buyGoodsReq.getBoughtNum() * sg.getHcoin()); - session.getPlayer().setCrystals(session.getPlayer().getCrystals() - buyGoodsReq.getBoughtNum() * sg.getMcoin()); - - if (!itemsCache.isEmpty()) { - for (GameItem gi : itemsCache.keySet()) { - session.getPlayer().getInventory().removeItem(gi, itemsCache.get(gi)); - } - itemsCache.clear(); - } session.getPlayer().addShopLimit(sg.getGoodsId(), buyGoodsReq.getBoughtNum(), ShopManager.getShopNextRefreshTime(sg)); GameItem item = new GameItem(GameData.getItemDataMap().get(sg.getGoodsItem().getId())); From 74783cfecd720433d1bdb9fa55a3dbb0388ada55 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Wed, 11 May 2022 23:34:22 +0930 Subject: [PATCH 6/8] More usage of payItems --- .../grasscutter/game/gacha/GachaManager.java | 2 +- .../grasscutter/game/inventory/Inventory.java | 8 + .../game/managers/InventoryManager.java | 183 +++++++----------- 3 files changed, 75 insertions(+), 118 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index c730a646a..160377913 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -128,7 +128,7 @@ public class GachaManager { } // Spend currency - if (banner.getCostItem() > 0 && !player.getInventory().payItems(new ItemParamData[] {new ItemParamData(banner.getCostItem(), times)})) { + if (banner.getCostItem() > 0 && !player.getInventory().payItem(banner.getCostItem(), times)) { return; } diff --git a/src/main/java/emu/grasscutter/game/inventory/Inventory.java b/src/main/java/emu/grasscutter/game/inventory/Inventory.java index 6936ca066..1af6038d9 100644 --- a/src/main/java/emu/grasscutter/game/inventory/Inventory.java +++ b/src/main/java/emu/grasscutter/game/inventory/Inventory.java @@ -272,6 +272,14 @@ public class Inventory implements Iterable { } } + public boolean payItem(int id, int count) { + return payItem(new ItemParamData(id, count)); + } + + public boolean payItem(ItemParamData costItem) { + return payItems(new ItemParamData[] {costItem}, 1, null); + } + public boolean payItems(ItemParamData[] costItems) { return payItems(costItems, 1, null); } diff --git a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java index f75f8b074..7c0288318 100644 --- a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java +++ b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java @@ -39,6 +39,8 @@ public class InventoryManager { 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 @@ -86,6 +88,7 @@ public class InventoryManager { int moraCost = 0; int expGain = 0; + List foodRelics = new ArrayList(); for (long guid : foodRelicList) { // Add to delete queue GameItem food = player.getInventory().getItemByGuid(guid); @@ -97,23 +100,21 @@ public class InventoryManager { expGain += food.getItemData().getBaseConvExp(); // Feeding artifact with exp already if (food.getTotalExp() > 0) { - expGain += (int) Math.floor(food.getTotalExp() * .8f); + expGain += (food.getTotalExp() * 4) / 5; } + foodRelics.add(food); } + List payList = new ArrayList(); for (ItemParam itemParam : list) { - GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemParam.getItemId()); - if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_RELIQUARY_MATERIAL) { - continue; - } - int amount = Math.min(food.getCount(), itemParam.getCount()); - int gain = 0; - if (food.getItemId() == RELIC_MATERIAL_2) { - gain = 10000 * amount; - } else if (food.getItemId() == RELIC_MATERIAL_1) { - gain = 2500 * amount; - } + 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; + }; expGain += gain; moraCost += gain; + payList.add(new ItemParamData(itemParam.getItemId(), itemParam.getCount())); } // Make sure exp gain is valid @@ -121,28 +122,14 @@ public class InventoryManager { return; } - // Check mora - if (player.getMora() < moraCost) { + // Confirm payment of materials and mora (assume food relics are payable afterwards) + payList.add(new ItemParamData(202, moraCost)); + if (!player.getInventory().payItems(payList.toArray(new ItemParamData[0]))) { return; } - player.setMora(player.getMora() - moraCost); - // Consume food items - for (long guid : foodRelicList) { - GameItem food = player.getInventory().getItemByGuid(guid); - if (food == null || !food.isDestroyable()) { - continue; - } - player.getInventory().removeItem(food); - } - for (ItemParam itemParam : list) { - GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemParam.getItemId()); - if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_RELIQUARY_MATERIAL) { - continue; - } - int amount = Math.min(food.getCount(), itemParam.getCount()); - player.getInventory().removeItem(food, amount); - } + // Consume food relics + player.getInventory().removeItems(foodRelics); // Implement random rate boost int rate = 1; @@ -232,22 +219,16 @@ public class InventoryManager { } expGain += food.getItemData().getWeaponBaseExp(); if (food.getTotalExp() > 0) { - expGain += (int) Math.floor(food.getTotalExp() * .8f); + expGain += (food.getTotalExp() * 4) / 5; } } for (ItemParam param : itemParamList) { - GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId()); - if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) { - continue; - } - int amount = Math.min(param.getCount(), food.getCount()); - if (food.getItemId() == WEAPON_ORE_3) { - expGain += 10000 * amount; - } else if (food.getItemId() == WEAPON_ORE_2) { - expGain += 2000 * amount; - } else if (food.getItemId() == WEAPON_ORE_1) { - expGain += 400 * amount; - } + 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; + }; } // Try @@ -289,65 +270,45 @@ public class InventoryManager { } // Get exp gain - int expGain = 0, moraCost = 0; - + int expGain = 0, expGainFree = 0; + List foodWeapons = new ArrayList(); for (long guid : foodWeaponGuidList) { GameItem food = player.getInventory().getItemByGuid(guid); if (food == null || !food.isDestroyable()) { continue; } expGain += food.getItemData().getWeaponBaseExp(); - moraCost += (int) Math.floor(food.getItemData().getWeaponBaseExp() * .1f); if (food.getTotalExp() > 0) { - expGain += (int) Math.floor(food.getTotalExp() * .8f); + expGainFree += (food.getTotalExp() * 4) / 5; // No tax :D } + foodWeapons.add(food); } + List payList = new ArrayList(); for (ItemParam param : itemParamList) { - GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId()); - if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) { - continue; - } - int amount = Math.min(param.getCount(), food.getCount()); - int gain = 0; - if (food.getItemId() == WEAPON_ORE_3) { - gain = 10000 * amount; - } else if (food.getItemId() == WEAPON_ORE_2) { - gain = 2000 * amount; - } else if (food.getItemId() == WEAPON_ORE_1) { - gain = 400 * amount; - } + 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; - moraCost += (int) Math.floor(gain * .1f); + payList.add(new ItemParamData(param.getItemId(), amount)); } // Make sure exp gain is valid + int moraCost = expGain / 10; + expGain += expGainFree; if (expGain <= 0) { return; } - - // Mora check - if (player.getMora() >= moraCost) { - player.setMora(player.getMora() - moraCost); - } else { + + // Confirm payment of materials and mora (assume food weapons are payable afterwards) + payList.add(new ItemParamData(202, moraCost)); + if (!player.getInventory().payItems(payList.toArray(new ItemParamData[0]))) { return; } - - // Consume weapon/items used to feed - for (long guid : foodWeaponGuidList) { - GameItem food = player.getInventory().getItemByGuid(guid); - if (food == null || !food.isDestroyable()) { - continue; - } - player.getInventory().removeItem(food); - } - for (ItemParam param : itemParamList) { - GameItem food = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(param.getItemId()); - if (food == null || food.getItemData().getMaterialType() != MaterialType.MATERIAL_WEAPON_EXP_STONE) { - continue; - } - int amount = Math.min(param.getCount(), food.getCount()); - player.getInventory().removeItem(food, amount); - } + player.getInventory().removeItems(foodWeapons); // Level up int maxLevel = promoteData.getUnlockMaxLevel(); @@ -394,7 +355,7 @@ public class InventoryManager { player.sendPacket(new PacketWeaponUpgradeRsp(weapon, oldLevel, leftovers)); } - private List getLeftoverOres(float leftover) { + private List getLeftoverOres(int leftover) { List leftoverOreList = new ArrayList<>(3); if (leftover < WEAPON_ORE_EXP_1) { @@ -402,11 +363,11 @@ public class InventoryManager { } // Get leftovers - int ore3 = (int) Math.floor(leftover / WEAPON_ORE_EXP_3); + int ore3 = leftover / WEAPON_ORE_EXP_3; leftover = leftover % WEAPON_ORE_EXP_3; - int ore2 = (int) Math.floor(leftover / WEAPON_ORE_EXP_2); + int ore2 = leftover / WEAPON_ORE_EXP_2; leftover = leftover % WEAPON_ORE_EXP_2; - int ore1 = (int) Math.floor(leftover / WEAPON_ORE_EXP_1); + int ore1 = leftover / WEAPON_ORE_EXP_1; if (ore3 > 0) { leftoverOreList.add(ItemParam.newBuilder().setItemId(WEAPON_ORE_3).setCount(ore3).build()); @@ -595,34 +556,25 @@ public class InventoryManager { return; } - GameItem feedItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId); - - if (feedItem == null || feedItem.getItemData().getMaterialType() != MaterialType.MATERIAL_EXP_FRUIT || feedItem.getCount() < count) { - return; - } - // Calc exp - int expGain = 0, moraCost = 0; + 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; + }; - // TODO clean up - if (itemId == AVATAR_BOOK_3) { - expGain = AVATAR_BOOK_EXP_3 * count; - } else if (itemId == AVATAR_BOOK_2) { - expGain = AVATAR_BOOK_EXP_2 * count; - } else if (itemId == AVATAR_BOOK_1) { - expGain = AVATAR_BOOK_EXP_1 * count; - } - moraCost = (int) Math.floor(expGain * .2f); - - // Mora check - if (player.getMora() >= moraCost) { - player.setMora(player.getMora() - moraCost); - } else { + // Sanity check + if (expGain <= 0) { + return; + } + + // Payment check + int moraCost = expGain / 5; + ItemParamData[] costItems = new ItemParamData[] {new ItemParamData(itemId, count), new ItemParamData(202, moraCost)}; + if (!player.getInventory().payItems(costItems)) { return; } - - // Consume items - player.getInventory().removeItem(feedItem, count); // Level up upgradeAvatar(player, avatar, promoteData, expGain); @@ -783,14 +735,11 @@ public class InventoryManager { return; } - GameItem costItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(talentData.getMainCostItemId()); - if (costItem == null || costItem.getCount() < talentData.getMainCostItemCount()) { + // Pay constellation item if possible + if (!player.getInventory().payItem(talentData.getMainCostItemId(), 1)) { return; } - // Consume item - player.getInventory().removeItem(costItem, talentData.getMainCostItemCount()); - // Apply + recalc avatar.getTalentIdList().add(talentData.getId()); avatar.setCoreProudSkillLevel(currentTalentLevel + 1); From e2817c696ac25bebdd1fbbbaed6b0d0824f286e6 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Thu, 12 May 2022 02:36:34 +0930 Subject: [PATCH 7/8] Fix immutable lists --- .../java/emu/grasscutter/game/managers/InventoryManager.java | 2 +- .../emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java index 7c0288318..6efe945aa 100644 --- a/src/main/java/emu/grasscutter/game/managers/InventoryManager.java +++ b/src/main/java/emu/grasscutter/game/managers/InventoryManager.java @@ -696,7 +696,7 @@ public class InventoryManager { } // Pay materials and mora if possible - List costs = proudSkill.getCostItems(); // Can this be null? + List costs = new ArrayList(proudSkill.getCostItems()); // Can this be null? if (proudSkill.getCoinCost() > 0) { costs.add(new ItemParamData(202, proudSkill.getCoinCost())); } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java index fc9564734..25cf05d24 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerBuyGoodsReq.java @@ -56,7 +56,7 @@ public class HandlerBuyGoodsReq extends PacketHandler { return; } - List costs = sg.getCostItemList(); // Can this even be null? + List costs = new ArrayList(sg.getCostItemList()); // Can this even be null? costs.add(new ItemParamData(202, sg.getScoin())); costs.add(new ItemParamData(201, sg.getHcoin())); costs.add(new ItemParamData(203, sg.getMcoin())); From 401b80060ef60879046ce1b7a1979a75f00d0075 Mon Sep 17 00:00:00 2001 From: Yazawazi <47273265+Yazawazi@users.noreply.github.com> Date: Thu, 12 May 2022 16:06:59 +0800 Subject: [PATCH 8/8] feature(widget): simple implementation of widget Simple implementation of a part of the gadget support, very rough, hard code variables, I am very sorry for that. It can now handle part of the gadget rig. More features and better support await PRs from others. if no one else does, I'll give it a try. --- proto/AbilityMixinWidgetMpSupport.proto | 8 +++ proto/AllWidgetDataNotify.proto | 29 +++++++++ proto/AnchorPointData.proto | 12 ++++ proto/AnchorPointDataNotify.proto | 18 ++++++ proto/AnchorPointOpReq.proto | 24 ++++++++ proto/AnchorPointOpRsp.proto | 18 ++++++ proto/ClientCollectorData.proto | 10 ++++ proto/ClientCollectorDataNotify.proto | 17 ++++++ proto/GetWidgetSlotReq.proto | 16 +++++ proto/GetWidgetSlotRsp.proto | 18 ++++++ proto/LunchBoxData.proto | 8 +++ proto/LunchBoxSlotType.proto | 9 +++ proto/OneoffGatherPointDetectorData.proto | 15 +++++ .../OneoffGatherPointDetectorDataNotify.proto | 17 ++++++ proto/QuickUseWidgetReq.proto | 26 ++++++++ proto/QuickUseWidgetRsp.proto | 21 +++++++ proto/SetUpLunchBoxWidgetReq.proto | 18 ++++++ proto/SetUpLunchBoxWidgetRsp.proto | 18 ++++++ proto/SetWidgetSlotReq.proto | 21 +++++++ proto/SetWidgetSlotRsp.proto | 21 +++++++ proto/UseWidgetCreateGadgetReq.proto | 20 +++++++ proto/UseWidgetCreateGadgetRsp.proto | 17 ++++++ proto/UseWidgetRetractGadgetReq.proto | 17 ++++++ proto/UseWidgetRetractGadgetRsp.proto | 18 ++++++ proto/WidgetActiveChangeNotify.proto | 17 ++++++ proto/WidgetCameraInfo.proto | 8 +++ proto/WidgetCoolDownData.proto | 10 ++++ proto/WidgetCoolDownNotify.proto | 18 ++++++ proto/WidgetCreateLocationInfo.proto | 10 ++++ proto/WidgetCreatorInfo.proto | 12 ++++ proto/WidgetCreatorOpType.proto | 9 +++ proto/WidgetDoBagReq.proto | 23 ++++++++ proto/WidgetDoBagRsp.proto | 17 ++++++ proto/WidgetGadgetAllDataNotify.proto | 17 ++++++ proto/WidgetGadgetData.proto | 9 +++ proto/WidgetGadgetDataNotify.proto | 17 ++++++ proto/WidgetGadgetDestroyNotify.proto | 16 +++++ proto/WidgetReportReq.proto | 19 ++++++ proto/WidgetReportRsp.proto | 17 ++++++ proto/WidgetSlotChangeNotify.proto | 19 ++++++ proto/WidgetSlotData.proto | 12 ++++ proto/WidgetSlotOp.proto | 8 +++ proto/WidgetSlotTag.proto | 8 +++ proto/WidgetSlotTagComparer.proto | 7 +++ proto/WidgetThunderBirdFeatherInfo.proto | 8 +++ ...getUseAttachAbilityGroupChangeNotify.proto | 17 ++++++ .../emu/grasscutter/game/player/Player.java | 13 ++++ .../packet/recv/HandlerGetWidgetSlotReq.java | 6 +- .../packet/recv/HandlerSetWidgetSlotReq.java | 33 +++++++++++ .../send/PacketAllWidgetDataNotify.java | 59 +++++++++++++++++++ .../packet/send/PacketGetWidgetSlotRsp.java | 41 +++++++++++++ .../packet/send/PacketSetWidgetSlotRsp.java | 18 ++++++ .../send/PacketWidgetGadgetAllDataNotify.java | 16 +++++ .../send/PacketWidgetSlotChangeNotify.java | 47 +++++++++++++++ 54 files changed, 946 insertions(+), 1 deletion(-) create mode 100644 proto/AbilityMixinWidgetMpSupport.proto create mode 100644 proto/AllWidgetDataNotify.proto create mode 100644 proto/AnchorPointData.proto create mode 100644 proto/AnchorPointDataNotify.proto create mode 100644 proto/AnchorPointOpReq.proto create mode 100644 proto/AnchorPointOpRsp.proto create mode 100644 proto/ClientCollectorData.proto create mode 100644 proto/ClientCollectorDataNotify.proto create mode 100644 proto/GetWidgetSlotReq.proto create mode 100644 proto/GetWidgetSlotRsp.proto create mode 100644 proto/LunchBoxData.proto create mode 100644 proto/LunchBoxSlotType.proto create mode 100644 proto/OneoffGatherPointDetectorData.proto create mode 100644 proto/OneoffGatherPointDetectorDataNotify.proto create mode 100644 proto/QuickUseWidgetReq.proto create mode 100644 proto/QuickUseWidgetRsp.proto create mode 100644 proto/SetUpLunchBoxWidgetReq.proto create mode 100644 proto/SetUpLunchBoxWidgetRsp.proto create mode 100644 proto/SetWidgetSlotReq.proto create mode 100644 proto/SetWidgetSlotRsp.proto create mode 100644 proto/UseWidgetCreateGadgetReq.proto create mode 100644 proto/UseWidgetCreateGadgetRsp.proto create mode 100644 proto/UseWidgetRetractGadgetReq.proto create mode 100644 proto/UseWidgetRetractGadgetRsp.proto create mode 100644 proto/WidgetActiveChangeNotify.proto create mode 100644 proto/WidgetCameraInfo.proto create mode 100644 proto/WidgetCoolDownData.proto create mode 100644 proto/WidgetCoolDownNotify.proto create mode 100644 proto/WidgetCreateLocationInfo.proto create mode 100644 proto/WidgetCreatorInfo.proto create mode 100644 proto/WidgetCreatorOpType.proto create mode 100644 proto/WidgetDoBagReq.proto create mode 100644 proto/WidgetDoBagRsp.proto create mode 100644 proto/WidgetGadgetAllDataNotify.proto create mode 100644 proto/WidgetGadgetData.proto create mode 100644 proto/WidgetGadgetDataNotify.proto create mode 100644 proto/WidgetGadgetDestroyNotify.proto create mode 100644 proto/WidgetReportReq.proto create mode 100644 proto/WidgetReportRsp.proto create mode 100644 proto/WidgetSlotChangeNotify.proto create mode 100644 proto/WidgetSlotData.proto create mode 100644 proto/WidgetSlotOp.proto create mode 100644 proto/WidgetSlotTag.proto create mode 100644 proto/WidgetSlotTagComparer.proto create mode 100644 proto/WidgetThunderBirdFeatherInfo.proto create mode 100644 proto/WidgetUseAttachAbilityGroupChangeNotify.proto create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerSetWidgetSlotReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketAllWidgetDataNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketGetWidgetSlotRsp.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketSetWidgetSlotRsp.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketWidgetGadgetAllDataNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketWidgetSlotChangeNotify.java diff --git a/proto/AbilityMixinWidgetMpSupport.proto b/proto/AbilityMixinWidgetMpSupport.proto new file mode 100644 index 000000000..e5323e892 --- /dev/null +++ b/proto/AbilityMixinWidgetMpSupport.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message AbilityMixinWidgetMpSupport { + uint32 target_entity_id = 1; +} diff --git a/proto/AllWidgetDataNotify.proto b/proto/AllWidgetDataNotify.proto new file mode 100644 index 000000000..252d56ce0 --- /dev/null +++ b/proto/AllWidgetDataNotify.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "LunchBoxData.proto"; +import "AnchorPointData.proto"; +import "OneoffGatherPointDetectorData.proto"; +import "ClientCollectorData.proto"; +import "WidgetCoolDownData.proto"; +import "WidgetSlotData.proto"; + +message AllWidgetDataNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4260; + } + + repeated AnchorPointData anchor_point_list = 2; + uint32 next_anchor_point_usable_time = 3; + LunchBoxData lunch_box_data = 5; + repeated OneoffGatherPointDetectorData oneoff_gather_point_detector_data_list = 6; + repeated ClientCollectorData client_collector_data_list = 7; + repeated WidgetCoolDownData cool_down_group_data_list = 8; + repeated WidgetCoolDownData normal_cool_down_data_list = 9; + repeated WidgetSlotData slot_list = 11; +} diff --git a/proto/AnchorPointData.proto b/proto/AnchorPointData.proto new file mode 100644 index 000000000..0bdbe0fef --- /dev/null +++ b/proto/AnchorPointData.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "Vector.proto"; + +message AnchorPointData { + uint32 anchor_point_id = 1; + Vector pos = 2; + Vector rot = 3; + uint32 end_time = 4; +} diff --git a/proto/AnchorPointDataNotify.proto b/proto/AnchorPointDataNotify.proto new file mode 100644 index 000000000..82153fd0d --- /dev/null +++ b/proto/AnchorPointDataNotify.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "AnchorPointData.proto"; + +message AnchorPointDataNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4253; + } + + repeated AnchorPointData anchor_point_list = 1; + uint32 next_usable_time = 2; +} diff --git a/proto/AnchorPointOpReq.proto b/proto/AnchorPointOpReq.proto new file mode 100644 index 000000000..2bfc2f3f7 --- /dev/null +++ b/proto/AnchorPointOpReq.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message AnchorPointOpReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4255; + } + + enum AnchorPointOpType { + ANCHOR_POINT_OP_NONE = 0; + ANCHOR_POINT_OP_TELEPORT = 1; + ANCHOR_POINT_OP_REMOVE = 2; + } + + uint32 anchor_point_op_type = 1; + uint32 anchor_point_id = 2; +} diff --git a/proto/AnchorPointOpRsp.proto b/proto/AnchorPointOpRsp.proto new file mode 100644 index 000000000..c5ce92aff --- /dev/null +++ b/proto/AnchorPointOpRsp.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message AnchorPointOpRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4274; + } + + int32 retcode = 1; + uint32 anchor_point_op_type = 2; + uint32 anchor_point_id = 3; +} diff --git a/proto/ClientCollectorData.proto b/proto/ClientCollectorData.proto new file mode 100644 index 000000000..e342d76c4 --- /dev/null +++ b/proto/ClientCollectorData.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message ClientCollectorData { + uint32 material_id = 1; + uint32 max_points = 2; + uint32 curr_points = 3; +} diff --git a/proto/ClientCollectorDataNotify.proto b/proto/ClientCollectorDataNotify.proto new file mode 100644 index 000000000..6fcfbaa51 --- /dev/null +++ b/proto/ClientCollectorDataNotify.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "ClientCollectorData.proto"; + +message ClientCollectorDataNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4280; + } + + repeated ClientCollectorData client_collector_data_list = 1; +} diff --git a/proto/GetWidgetSlotReq.proto b/proto/GetWidgetSlotReq.proto new file mode 100644 index 000000000..cf43e24b4 --- /dev/null +++ b/proto/GetWidgetSlotReq.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message GetWidgetSlotReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4285; + } + +} diff --git a/proto/GetWidgetSlotRsp.proto b/proto/GetWidgetSlotRsp.proto new file mode 100644 index 000000000..96a00c35a --- /dev/null +++ b/proto/GetWidgetSlotRsp.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetSlotData.proto"; + +message GetWidgetSlotRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4291; + } + + int32 retcode = 1; + repeated WidgetSlotData slot_list = 2; +} diff --git a/proto/LunchBoxData.proto b/proto/LunchBoxData.proto new file mode 100644 index 000000000..dc099eb0f --- /dev/null +++ b/proto/LunchBoxData.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message LunchBoxData { + map slot_material_map = 1; +} diff --git a/proto/LunchBoxSlotType.proto b/proto/LunchBoxSlotType.proto new file mode 100644 index 000000000..f2ab1f56a --- /dev/null +++ b/proto/LunchBoxSlotType.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +enum LunchBoxSlotType { + LUNCH_BOX_SLOT_NONE = 0; + LUNCH_BOX_SLOT_REVIVE = 1; + LUNCH_BOX_SLOT_HEAL = 2; +} diff --git a/proto/OneoffGatherPointDetectorData.proto b/proto/OneoffGatherPointDetectorData.proto new file mode 100644 index 000000000..deec47762 --- /dev/null +++ b/proto/OneoffGatherPointDetectorData.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "Vector.proto"; + +message OneoffGatherPointDetectorData { + uint32 material_id = 1; + bool is_all_collected = 2; + bool is_hint_valid = 3; + Vector hint_center_pos = 4; + uint32 hint_radius = 5; + uint32 group_id = 6; + uint32 config_id = 7; +} diff --git a/proto/OneoffGatherPointDetectorDataNotify.proto b/proto/OneoffGatherPointDetectorDataNotify.proto new file mode 100644 index 000000000..d8911c232 --- /dev/null +++ b/proto/OneoffGatherPointDetectorDataNotify.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "OneoffGatherPointDetectorData.proto"; + +message OneoffGatherPointDetectorDataNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4288; + } + + repeated OneoffGatherPointDetectorData oneoff_gather_point_detector_data_list = 1; +} diff --git a/proto/QuickUseWidgetReq.proto b/proto/QuickUseWidgetReq.proto new file mode 100644 index 000000000..86b912c84 --- /dev/null +++ b/proto/QuickUseWidgetReq.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetCreateLocationInfo.proto"; +import "WidgetCameraInfo.proto"; +import "WidgetCreatorInfo.proto"; +import "WidgetThunderBirdFeatherInfo.proto"; + +message QuickUseWidgetReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4300; + } + + oneof Param { + WidgetCreateLocationInfo location_info = 20; + WidgetCameraInfo camera_info = 21; + WidgetCreatorInfo creator_info = 22; + WidgetThunderBirdFeatherInfo thunder_bird_feather_info = 23; + } +} diff --git a/proto/QuickUseWidgetRsp.proto b/proto/QuickUseWidgetRsp.proto new file mode 100644 index 000000000..8a6f9a957 --- /dev/null +++ b/proto/QuickUseWidgetRsp.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "OneoffGatherPointDetectorData.proto"; +import "ClientCollectorData.proto"; + +message QuickUseWidgetRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4258; + } + + int32 retcode = 1; + uint32 material_id = 2; + OneoffGatherPointDetectorData detector_data = 3; + ClientCollectorData client_collector_data = 4; +} diff --git a/proto/SetUpLunchBoxWidgetReq.proto b/proto/SetUpLunchBoxWidgetReq.proto new file mode 100644 index 000000000..a3e0293c3 --- /dev/null +++ b/proto/SetUpLunchBoxWidgetReq.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "LunchBoxData.proto"; + +message SetUpLunchBoxWidgetReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4265; + } + + LunchBoxData lunch_box_data = 1; +} diff --git a/proto/SetUpLunchBoxWidgetRsp.proto b/proto/SetUpLunchBoxWidgetRsp.proto new file mode 100644 index 000000000..e2c70dd0f --- /dev/null +++ b/proto/SetUpLunchBoxWidgetRsp.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "LunchBoxData.proto"; + +message SetUpLunchBoxWidgetRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4297; + } + + int32 retcode = 1; + LunchBoxData lunch_box_data = 2; +} diff --git a/proto/SetWidgetSlotReq.proto b/proto/SetWidgetSlotReq.proto new file mode 100644 index 000000000..2d140a528 --- /dev/null +++ b/proto/SetWidgetSlotReq.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetSlotOp.proto"; +import "WidgetSlotTag.proto"; + +message SetWidgetSlotReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4269; + } + + WidgetSlotOp op = 2; + repeated WidgetSlotTag tag_list = 3; + uint32 material_id = 4; +} diff --git a/proto/SetWidgetSlotRsp.proto b/proto/SetWidgetSlotRsp.proto new file mode 100644 index 000000000..7d7b34a3e --- /dev/null +++ b/proto/SetWidgetSlotRsp.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetSlotOp.proto"; +import "WidgetSlotTag.proto"; + +message SetWidgetSlotRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4279; + } + + int32 retcode = 1; + WidgetSlotOp op = 2; + repeated WidgetSlotTag tag_list = 3; + uint32 material_id = 4; +} diff --git a/proto/UseWidgetCreateGadgetReq.proto b/proto/UseWidgetCreateGadgetReq.proto new file mode 100644 index 000000000..4acc7696b --- /dev/null +++ b/proto/UseWidgetCreateGadgetReq.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "Vector.proto"; + +message UseWidgetCreateGadgetReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4276; + } + + uint32 material_id = 1; + Vector pos = 2; + Vector rot = 3; +} diff --git a/proto/UseWidgetCreateGadgetRsp.proto b/proto/UseWidgetCreateGadgetRsp.proto new file mode 100644 index 000000000..74c86f42c --- /dev/null +++ b/proto/UseWidgetCreateGadgetRsp.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message UseWidgetCreateGadgetRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4270; + } + + int32 retcode = 1; + uint32 material_id = 2; +} diff --git a/proto/UseWidgetRetractGadgetReq.proto b/proto/UseWidgetRetractGadgetReq.proto new file mode 100644 index 000000000..e7144ee76 --- /dev/null +++ b/proto/UseWidgetRetractGadgetReq.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message UseWidgetRetractGadgetReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4273; + } + + uint32 entity_id = 1; +} diff --git a/proto/UseWidgetRetractGadgetRsp.proto b/proto/UseWidgetRetractGadgetRsp.proto new file mode 100644 index 000000000..4bf69bbc9 --- /dev/null +++ b/proto/UseWidgetRetractGadgetRsp.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message UseWidgetRetractGadgetRsp { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4266; + } + + int32 retcode = 1; + uint32 entity_id = 2; +} diff --git a/proto/WidgetActiveChangeNotify.proto b/proto/WidgetActiveChangeNotify.proto new file mode 100644 index 000000000..ee5033438 --- /dev/null +++ b/proto/WidgetActiveChangeNotify.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetSlotData.proto"; + +message WidgetActiveChangeNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4264; + } + + repeated WidgetSlotData widget_data_list = 1; +} diff --git a/proto/WidgetCameraInfo.proto b/proto/WidgetCameraInfo.proto new file mode 100644 index 000000000..8f8d659af --- /dev/null +++ b/proto/WidgetCameraInfo.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetCameraInfo { + uint32 target_entity_id = 1; +} diff --git a/proto/WidgetCoolDownData.proto b/proto/WidgetCoolDownData.proto new file mode 100644 index 000000000..09b3d235f --- /dev/null +++ b/proto/WidgetCoolDownData.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetCoolDownData { + uint32 id = 1; + uint64 cool_down_time = 2; + bool is_success = 3; +} diff --git a/proto/WidgetCoolDownNotify.proto b/proto/WidgetCoolDownNotify.proto new file mode 100644 index 000000000..f034cc79f --- /dev/null +++ b/proto/WidgetCoolDownNotify.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetCoolDownData.proto"; + +message WidgetCoolDownNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4298; + } + + repeated WidgetCoolDownData group_cool_down_data_list = 1; + repeated WidgetCoolDownData normal_cool_down_data_list = 2; +} diff --git a/proto/WidgetCreateLocationInfo.proto b/proto/WidgetCreateLocationInfo.proto new file mode 100644 index 000000000..0f4c7178f --- /dev/null +++ b/proto/WidgetCreateLocationInfo.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "Vector.proto"; + +message WidgetCreateLocationInfo { + Vector pos = 1; + Vector rot = 2; +} diff --git a/proto/WidgetCreatorInfo.proto b/proto/WidgetCreatorInfo.proto new file mode 100644 index 000000000..1b277630c --- /dev/null +++ b/proto/WidgetCreatorInfo.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetCreatorOpType.proto"; +import "WidgetCreateLocationInfo.proto"; + +message WidgetCreatorInfo { + WidgetCreatorOpType op_type = 1; + uint32 entity_id = 2; + WidgetCreateLocationInfo location_info = 3; +} diff --git a/proto/WidgetCreatorOpType.proto b/proto/WidgetCreatorOpType.proto new file mode 100644 index 000000000..b2eaab3e3 --- /dev/null +++ b/proto/WidgetCreatorOpType.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +enum WidgetCreatorOpType { + WIDGET_CREATOR_TYPE_NONE = 0; + WIDGET_CREATOR_TYPE_RETRACT = 1; + WIDGET_CREATOR_TYPE_RETRACT_AND_CREATE = 2; +} diff --git a/proto/WidgetDoBagReq.proto b/proto/WidgetDoBagReq.proto new file mode 100644 index 000000000..cbdb3460d --- /dev/null +++ b/proto/WidgetDoBagReq.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetCreateLocationInfo.proto"; +import "WidgetCreatorInfo.proto"; + +message WidgetDoBagReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4290; + } + + oneof OpInfo { + WidgetCreateLocationInfo location_info = 20; + WidgetCreatorInfo widget_creator_info = 21; + } + uint32 material_id = 1; +} diff --git a/proto/WidgetDoBagRsp.proto b/proto/WidgetDoBagRsp.proto new file mode 100644 index 000000000..898294d1d --- /dev/null +++ b/proto/WidgetDoBagRsp.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetDoBagRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4271; + } + + int32 retcode = 1; + uint32 material_id = 2; +} diff --git a/proto/WidgetGadgetAllDataNotify.proto b/proto/WidgetGadgetAllDataNotify.proto new file mode 100644 index 000000000..ea9034f37 --- /dev/null +++ b/proto/WidgetGadgetAllDataNotify.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetGadgetData.proto"; + +message WidgetGadgetAllDataNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4262; + } + + repeated WidgetGadgetData widget_gadget_data = 1; +} diff --git a/proto/WidgetGadgetData.proto b/proto/WidgetGadgetData.proto new file mode 100644 index 000000000..1c2756d21 --- /dev/null +++ b/proto/WidgetGadgetData.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetGadgetData { + uint32 gadget_id = 1; + repeated uint32 gadget_entity_id_list = 3; +} diff --git a/proto/WidgetGadgetDataNotify.proto b/proto/WidgetGadgetDataNotify.proto new file mode 100644 index 000000000..b056941c7 --- /dev/null +++ b/proto/WidgetGadgetDataNotify.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetGadgetData.proto"; + +message WidgetGadgetDataNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4256; + } + + WidgetGadgetData widget_gadget_data = 1; +} diff --git a/proto/WidgetGadgetDestroyNotify.proto b/proto/WidgetGadgetDestroyNotify.proto new file mode 100644 index 000000000..3d6287fc0 --- /dev/null +++ b/proto/WidgetGadgetDestroyNotify.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetGadgetDestroyNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4268; + } + + uint32 entity_id = 1; +} diff --git a/proto/WidgetReportReq.proto b/proto/WidgetReportReq.proto new file mode 100644 index 000000000..21eed4dfc --- /dev/null +++ b/proto/WidgetReportReq.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetReportReq { + enum CmdId { + option allow_alias = true; + ENET_CHANNEL_ID = 0; + NONE = 0; + ENET_IS_RELIABLE = 1; + IS_ALLOW_CLIENT = 1; + CMD_ID = 4294; + } + + uint32 material_id = 1; + bool is_clear_hint = 2; + bool is_client_collect = 3; +} diff --git a/proto/WidgetReportRsp.proto b/proto/WidgetReportRsp.proto new file mode 100644 index 000000000..3142df9b3 --- /dev/null +++ b/proto/WidgetReportRsp.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetReportRsp { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4259; + } + + int32 retcode = 1; + uint32 material_id = 2; +} diff --git a/proto/WidgetSlotChangeNotify.proto b/proto/WidgetSlotChangeNotify.proto new file mode 100644 index 000000000..3ef12560f --- /dev/null +++ b/proto/WidgetSlotChangeNotify.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetSlotOp.proto"; +import "WidgetSlotData.proto"; + +message WidgetSlotChangeNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4289; + } + + WidgetSlotOp op = 1; + WidgetSlotData slot = 2; +} diff --git a/proto/WidgetSlotData.proto b/proto/WidgetSlotData.proto new file mode 100644 index 000000000..095915d18 --- /dev/null +++ b/proto/WidgetSlotData.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "WidgetSlotTag.proto"; + +message WidgetSlotData { + WidgetSlotTag tag = 1; + uint32 material_id = 2; + uint32 cd_over_time = 3; + bool is_active = 4; +} diff --git a/proto/WidgetSlotOp.proto b/proto/WidgetSlotOp.proto new file mode 100644 index 000000000..d4fa27538 --- /dev/null +++ b/proto/WidgetSlotOp.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +enum WidgetSlotOp { + ATTACH = 0; + DETACH = 1; +} diff --git a/proto/WidgetSlotTag.proto b/proto/WidgetSlotTag.proto new file mode 100644 index 000000000..edfd6c06a --- /dev/null +++ b/proto/WidgetSlotTag.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +enum WidgetSlotTag { + WIDGET_SLOT_QUICK_USE = 0; + WIDGET_SLOT_ATTACH_AVATAR = 1; +} diff --git a/proto/WidgetSlotTagComparer.proto b/proto/WidgetSlotTagComparer.proto new file mode 100644 index 000000000..5f6253c17 --- /dev/null +++ b/proto/WidgetSlotTagComparer.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetSlotTagComparer { +} diff --git a/proto/WidgetThunderBirdFeatherInfo.proto b/proto/WidgetThunderBirdFeatherInfo.proto new file mode 100644 index 000000000..cad88c47a --- /dev/null +++ b/proto/WidgetThunderBirdFeatherInfo.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetThunderBirdFeatherInfo { + repeated uint32 entity_id_list = 1; +} diff --git a/proto/WidgetUseAttachAbilityGroupChangeNotify.proto b/proto/WidgetUseAttachAbilityGroupChangeNotify.proto new file mode 100644 index 000000000..85fa84345 --- /dev/null +++ b/proto/WidgetUseAttachAbilityGroupChangeNotify.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + + +message WidgetUseAttachAbilityGroupChangeNotify { + enum CmdId { + option allow_alias = true; + NONE = 0; + ENET_CHANNEL_ID = 0; + ENET_IS_RELIABLE = 1; + CMD_ID = 4292; + } + + uint32 material_id = 1; + bool is_attach = 2; +} diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 58e500b97..0be6c7a54 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -82,6 +82,8 @@ public class Player { private Set flyCloakList; private Set costumeList; + private Integer widgetId; + @Transient private long nextGuid = 0; @Transient private int peerId; @Transient private World world; @@ -297,6 +299,14 @@ public class Player { this.updateProfile(); } + public Integer getWidgetId() { + return widgetId; + } + + public void setWidgetId(Integer widgetId) { + this.widgetId = widgetId; + } + public Position getPos() { return pos; } @@ -1141,6 +1151,9 @@ public class Player { session.send(new PacketPlayerStoreNotify(this)); session.send(new PacketAvatarDataNotify(this)); + session.send(new PacketAllWidgetDataNotify(this)); + session.send(new PacketWidgetGadgetAllDataNotify()); + getTodayMoonCard(); // The timer works at 0:0, some users log in after that, use this method to check if they have received a reward today or not. If not, send the reward. session.send(new PacketPlayerEnterSceneNotify(this)); // Enter game world diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetWidgetSlotReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetWidgetSlotReq.java index b41a6cc1d..e7b352122 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetWidgetSlotReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetWidgetSlotReq.java @@ -1,16 +1,20 @@ package emu.grasscutter.server.packet.recv; +import emu.grasscutter.game.player.Player; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketGetShopRsp; +import emu.grasscutter.server.packet.send.PacketGetWidgetSlotRsp; @Opcodes(PacketOpcodes.GetWidgetSlotReq) public class HandlerGetWidgetSlotReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - // Unhandled + Player player = session.getPlayer(); + session.send(new PacketGetWidgetSlotRsp(player)); } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetWidgetSlotReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetWidgetSlotReq.java new file mode 100644 index 000000000..6f55e2ab9 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerSetWidgetSlotReq.java @@ -0,0 +1,33 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetWidgetSlotReqOuterClass; +import emu.grasscutter.net.proto.WidgetSlotOpOuterClass; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketSetWidgetSlotRsp; +import emu.grasscutter.server.packet.send.PacketWidgetSlotChangeNotify; + +@Opcodes(PacketOpcodes.SetWidgetSlotReq) +public class HandlerSetWidgetSlotReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + SetWidgetSlotReqOuterClass.SetWidgetSlotReq req = SetWidgetSlotReqOuterClass.SetWidgetSlotReq.parseFrom(payload); + + Player player = session.getPlayer(); + player.setWidgetId(req.getMaterialId()); + + // WidgetSlotChangeNotify op & slot key + session.send(new PacketWidgetSlotChangeNotify(WidgetSlotOpOuterClass.WidgetSlotOp.DETACH)); + // WidgetSlotChangeNotify slot + session.send(new PacketWidgetSlotChangeNotify(req.getMaterialId())); + + // SetWidgetSlotRsp + session.send(new PacketSetWidgetSlotRsp(req.getMaterialId())); + } + +} + diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketAllWidgetDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketAllWidgetDataNotify.java new file mode 100644 index 000000000..c52cc1594 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketAllWidgetDataNotify.java @@ -0,0 +1,59 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.AllWidgetDataNotifyOuterClass.AllWidgetDataNotify; +import emu.grasscutter.net.proto.LunchBoxDataOuterClass; +import emu.grasscutter.net.proto.WidgetSlotDataOuterClass; +import emu.grasscutter.net.proto.WidgetSlotTagOuterClass; + +import java.util.List; +import java.util.Map; + +public class PacketAllWidgetDataNotify extends BasePacket { + + public PacketAllWidgetDataNotify(Player player) { + super(PacketOpcodes.AllWidgetDataNotify); + + // TODO: Implement this + + AllWidgetDataNotify.Builder proto = AllWidgetDataNotify.newBuilder() + // If you want to implement this, feel free to do so. :) + .setLunchBoxData( + LunchBoxDataOuterClass.LunchBoxData.newBuilder().build() + ) + // Maybe it's a little difficult, or it makes you upset :( + .addAllOneoffGatherPointDetectorDataList(List.of()) + // So, goodbye, and hopefully sometime in the future o(* ̄▽ ̄*)ブ + .addAllCoolDownGroupDataList(List.of()) + // I'll see your PR with a title that says (・∀・(・∀・(・∀・*) + .addAllAnchorPointList(List.of()) + // "Complete implementation of widget functionality" b( ̄▽ ̄)d  + .addAllClientCollectorDataList(List.of()) + // Good luck, my boy. + .addAllNormalCoolDownDataList(List.of()); + + if (player.getWidgetId() == null) { + proto.addAllSlotList(List.of()); + } else { + proto.addSlotList( + WidgetSlotDataOuterClass.WidgetSlotData.newBuilder() + .setIsActive(true) + .setMaterialId(player.getWidgetId()) + .build() + ); + + proto.addSlotList( + WidgetSlotDataOuterClass.WidgetSlotData.newBuilder() + .setTag(WidgetSlotTagOuterClass.WidgetSlotTag.WIDGET_SLOT_ATTACH_AVATAR) + .build() + ); + } + + AllWidgetDataNotify protoData = proto.build(); + + this.setData(protoData); + } +} + diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketGetWidgetSlotRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketGetWidgetSlotRsp.java new file mode 100644 index 000000000..a4e8a2ea9 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketGetWidgetSlotRsp.java @@ -0,0 +1,41 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.GetWidgetSlotRspOuterClass; +import emu.grasscutter.net.proto.WidgetSlotDataOuterClass; +import emu.grasscutter.net.proto.WidgetSlotTagOuterClass; + +import java.util.List; + +public class PacketGetWidgetSlotRsp extends BasePacket { + + public PacketGetWidgetSlotRsp(Player player) { + super(PacketOpcodes.GetWidgetSlotRsp); + + GetWidgetSlotRspOuterClass.GetWidgetSlotRsp.Builder proto = + GetWidgetSlotRspOuterClass.GetWidgetSlotRsp.newBuilder(); + + if (player.getWidgetId() == null) { + proto.addAllSlotList(List.of()); + } else { + proto.addSlotList( + WidgetSlotDataOuterClass.WidgetSlotData.newBuilder() + .setIsActive(true) + .setMaterialId(player.getWidgetId()) + .build() + ); + + proto.addSlotList( + WidgetSlotDataOuterClass.WidgetSlotData.newBuilder() + .setTag(WidgetSlotTagOuterClass.WidgetSlotTag.WIDGET_SLOT_ATTACH_AVATAR) + .build() + ); + } + + GetWidgetSlotRspOuterClass.GetWidgetSlotRsp protoData = proto.build(); + + this.setData(protoData); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketSetWidgetSlotRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketSetWidgetSlotRsp.java new file mode 100644 index 000000000..0f81afa85 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketSetWidgetSlotRsp.java @@ -0,0 +1,18 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.SetWidgetSlotRspOuterClass; + +public class PacketSetWidgetSlotRsp extends BasePacket { + + public PacketSetWidgetSlotRsp(int materialId) { + super(PacketOpcodes.SetWidgetSlotRsp); + + SetWidgetSlotRspOuterClass.SetWidgetSlotRsp proto = SetWidgetSlotRspOuterClass.SetWidgetSlotRsp.newBuilder() + .setMaterialId(materialId) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetGadgetAllDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetGadgetAllDataNotify.java new file mode 100644 index 000000000..b0000efb7 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetGadgetAllDataNotify.java @@ -0,0 +1,16 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WidgetGadgetAllDataNotifyOuterClass.WidgetGadgetAllDataNotify; + +public class PacketWidgetGadgetAllDataNotify extends BasePacket { + + public PacketWidgetGadgetAllDataNotify() { + super(PacketOpcodes.AllWidgetDataNotify); + + WidgetGadgetAllDataNotify proto = WidgetGadgetAllDataNotify.newBuilder().build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetSlotChangeNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetSlotChangeNotify.java new file mode 100644 index 000000000..ab0ace7eb --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetSlotChangeNotify.java @@ -0,0 +1,47 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WidgetSlotChangeNotifyOuterClass; +import emu.grasscutter.net.proto.WidgetSlotDataOuterClass; +import emu.grasscutter.net.proto.WidgetSlotOpOuterClass; + +public class PacketWidgetSlotChangeNotify extends BasePacket { + + public PacketWidgetSlotChangeNotify(WidgetSlotChangeNotifyOuterClass.WidgetSlotChangeNotify proto) { + super(PacketOpcodes.WidgetSlotChangeNotify); + + this.setData(proto); + } + + public PacketWidgetSlotChangeNotify(WidgetSlotOpOuterClass.WidgetSlotOp op) { + super(PacketOpcodes.WidgetSlotChangeNotify); + + WidgetSlotChangeNotifyOuterClass.WidgetSlotChangeNotify proto = WidgetSlotChangeNotifyOuterClass.WidgetSlotChangeNotify.newBuilder() + .setOp(op) + .setSlot( + WidgetSlotDataOuterClass.WidgetSlotData.newBuilder() + .setIsActive(true) + .build() + ) + .build(); + + this.setData(proto); + } + + public PacketWidgetSlotChangeNotify(int materialId) { + super(PacketOpcodes.WidgetSlotChangeNotify); + + WidgetSlotChangeNotifyOuterClass.WidgetSlotChangeNotify proto = WidgetSlotChangeNotifyOuterClass.WidgetSlotChangeNotify.newBuilder() + .setSlot( + WidgetSlotDataOuterClass.WidgetSlotData.newBuilder() + .setIsActive(true) + .setMaterialId(materialId) + .build() + ) + .build(); + + this.setData(proto); + } + +}