diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 3cf8363e7..a986ff7f8 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -6,6 +6,7 @@ import java.util.Calendar; import emu.grasscutter.auth.AuthenticationSystem; import emu.grasscutter.auth.DefaultAuthentication; import emu.grasscutter.command.CommandMap; +import emu.grasscutter.game.managers.EnergyManager.EnergyManager; import emu.grasscutter.game.managers.StaminaManager.StaminaManager; import emu.grasscutter.plugin.PluginManager; import emu.grasscutter.plugin.api.ServerHook; @@ -106,6 +107,7 @@ public final class Grasscutter { Grasscutter.updateDayOfWeek(); ResourceLoader.loadAll(); ScriptLoader.init(); + EnergyManager.initialize(); // Initialize database. DatabaseManager.initialize(); diff --git a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java index 491aaa3f4..edbf4840a 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityMonster.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityMonster.java @@ -5,6 +5,7 @@ import emu.grasscutter.data.common.PropGrowCurve; import emu.grasscutter.data.def.MonsterCurveData; import emu.grasscutter.data.def.MonsterData; import emu.grasscutter.game.dungeons.DungeonChallenge; +import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.PlayerProperty; @@ -111,6 +112,23 @@ public class EntityMonster extends GameEntity { this.poseId = poseId; } + @Override + public void damage(float amount, int killerId) { + // Get HP before damage. + float hpBeforeDamage = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + + // Apply damage. + super.damage(amount, killerId); + + // Get HP after damage. + float hpAfterDamage = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP); + + // Invoke energy drop logic. + for (Player player : this.getScene().getPlayers()) { + player.getEnergyManager().handleMonsterEnergyDrop(this, hpBeforeDamage, hpAfterDamage); + } + } + @Override public void onDeath(int killerId) { if (this.getSpawnEntry() != null) { diff --git a/src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyDropEntry.java b/src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyDropEntry.java new file mode 100644 index 000000000..a26140521 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyDropEntry.java @@ -0,0 +1,16 @@ +package emu.grasscutter.game.managers.EnergyManager; + +import java.util.List; + +public class EnergyDropEntry { + private int dropId; + private List dropList; + + public int getDropId() { + return this.dropId; + } + + public List getDropList() { + return this.dropList; + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyDropInfo.java b/src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyDropInfo.java new file mode 100644 index 000000000..40145c545 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyDropInfo.java @@ -0,0 +1,14 @@ +package emu.grasscutter.game.managers.EnergyManager; + +public class EnergyDropInfo { + private int ballId; + private int count; + + public int getBallId() { + return this.ballId; + } + + public int getCount() { + return this.count; + } +} diff --git a/src/main/java/emu/grasscutter/game/managers/EnergyManager.java b/src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyManager.java similarity index 69% rename from src/main/java/emu/grasscutter/game/managers/EnergyManager.java rename to src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyManager.java index 4eba7cf62..c48b6c270 100644 --- a/src/main/java/emu/grasscutter/game/managers/EnergyManager.java +++ b/src/main/java/emu/grasscutter/game/managers/EnergyManager/EnergyManager.java @@ -1,30 +1,43 @@ -package emu.grasscutter.game.managers; +package emu.grasscutter.game.managers.EnergyManager; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.DataLoader; import emu.grasscutter.data.GameData; import emu.grasscutter.data.def.AvatarSkillDepotData; import emu.grasscutter.data.def.ItemData; +import emu.grasscutter.data.def.MonsterData.HpDrops; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.EntityClientGadget; import emu.grasscutter.game.entity.EntityItem; +import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.ElementType; +import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.AbilityActionGenerateElemBall; import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry; import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.utils.Position; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import static emu.grasscutter.Configuration.GAME_OPTIONS; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.Collection; +import java.util.List; import java.util.Optional; +import com.google.gson.reflect.TypeToken; import com.google.protobuf.InvalidProtocolBufferException; public class EnergyManager { private final Player player; + private final static Int2ObjectMap> energyDropData = new Int2ObjectOpenHashMap<>(); public EnergyManager(Player player) { this.player = player; @@ -34,6 +47,22 @@ public class EnergyManager { return this.player; } + public static void initialize() { + // Read the data we need for monster energy drops. + try (Reader fileReader = new InputStreamReader(DataLoader.load("EnergyDrop.json"))) { + List energyDropList = Grasscutter.getGsonFactory().fromJson(fileReader, TypeToken.getParameterized(Collection.class, EnergyDropEntry.class).getType()); + + for (EnergyDropEntry entry : energyDropList) { + energyDropData.put(entry.getDropId(), entry.getDropList()); + } + + Grasscutter.getLogger().info("Energy drop data successfully loaded."); + } + catch (Exception ex) { + Grasscutter.getLogger().error("Unable to load energy drop data.", ex); + } + } + /********** Particle creation for elemental skills. **********/ @@ -117,17 +146,8 @@ public class EnergyManager { } } - // Get the item data for an energy particle of the correct element. - ItemData itemData = GameData.getItemDataMap().get(itemId); - if (itemData == null) { - return; // Should never happen - } - - // Generate entity. - EntityItem energyBall = new EntityItem(getPlayer().getScene(), getPlayer(), itemData, new Position(action.getPos()), 1); - energyBall.getRotation().set(action.getRot()); - - this.getPlayer().getScene().addEntity(energyBall); + // Generate the particle/orb. + generateElemBall(itemId, new Position(action.getPos()), 1); } /********** @@ -183,11 +203,10 @@ public class EnergyManager { float elementBonus = (ballElement == null) ? 2.0f : (avatarElement == ballElement) ? 3.0f : 1.0f; // Add the energy. - entity.addEnergy(baseEnergy * elementBonus * offFieldPenalty, PropChangeReason.PROP_CHANGE_ENERGY_BALL); + entity.addEnergy(baseEnergy * elementBonus * offFieldPenalty * elemBall.getCount(), PropChangeReason.PROP_CHANGE_ENERGY_BALL); } } - /********** Energy logic related to using skills. **********/ @@ -218,4 +237,56 @@ public class EnergyManager { // Handle elemental burst. this.handleBurstCast(avatar, skillId); } + + /********** + Monster energy drops. + **********/ + private void generateElemBallDrops(EntityMonster monster, int dropId) { + // Generate all drops specified for the given drop id. + if (!energyDropData.containsKey(dropId)) { + Grasscutter.getLogger().warn("No drop data for dropId {} found.", dropId); + return; + } + + for (EnergyDropInfo info : energyDropData.get(dropId)) { + this.generateElemBall(info.getBallId(), monster.getPosition(), info.getCount()); + } + } + public void handleMonsterEnergyDrop(EntityMonster monster, float hpBeforeDamage, float hpAfterDamage) { + // Calculate the HP tresholds for before and after the damage was taken. + float maxHp = monster.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); + float thresholdBefore = hpBeforeDamage / maxHp; + float thresholdAfter = hpAfterDamage / maxHp; + + // Determine the thresholds the monster has passed, and generate drops based on that. + for (HpDrops drop : monster.getMonsterData().getHpDrops()) { + if (drop.getDropId() == 0) { + continue; + } + + float threshold = drop.getHpPercent() / 100.0f; + if (threshold < thresholdBefore && threshold >= thresholdAfter) { + generateElemBallDrops(monster, drop.getDropId()); + } + } + + // Handle kill drops. + if (hpAfterDamage <= 0 && monster.getMonsterData().getKillDropId() != 0) { + generateElemBallDrops(monster, monster.getMonsterData().getKillDropId()); + } + } + + /********** + Utility. + **********/ + private void generateElemBall(int ballId, Position position, int count) { + // Generate a particle/orb with the specified parameters. + ItemData itemData = GameData.getItemDataMap().get(ballId); + if (itemData == null) { + return; + } + + EntityItem energyBall = new EntityItem(this.getPlayer().getScene(), this.getPlayer(), itemData, position, count); + this.getPlayer().getScene().addEntity(energyBall); + } } diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 1d1f80e51..1eac62faa 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -23,8 +23,8 @@ import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.mail.MailHandler; import emu.grasscutter.game.managers.StaminaManager.StaminaManager; -import emu.grasscutter.game.managers.EnergyManager; import emu.grasscutter.game.managers.SotSManager; +import emu.grasscutter.game.managers.EnergyManager.EnergyManager; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.PlayerProperty; diff --git a/src/main/resources/defaults/data/EnergyDrop.json b/src/main/resources/defaults/data/EnergyDrop.json new file mode 100644 index 000000000..2e3b72cf1 --- /dev/null +++ b/src/main/resources/defaults/data/EnergyDrop.json @@ -0,0 +1,160 @@ +[ + { + "dropId": 22010010, + "dropList": [ + { "ballId": 2024, "count": 1 } + ] + }, + { + "dropId": 22010030, + "dropList": [ + { "ballId": 2008, "count": 1 } + ] + }, + { + "dropId": 22010050, + "dropList": [ + { "ballId": 2024, "count": 3 }, + { "ballId": 2008, "count": 1 } + ] + }, + { + "dropId": 22010013, + "dropList": [ + { "ballId": 2019, "count": 1 } + ] + }, + { + "dropId": 22010033, + "dropList": [ + { "ballId": 2003, "count": 1 } + ] + }, + { + "dropId": 22010015, + "dropList": [ + { "ballId": 2021, "count": 1 } + ] + }, + { + "dropId": 22010035, + "dropList": [ + { "ballId": 2005, "count": 1 } + ] + }, + { + "dropId": 22010034, + "dropList": [ + { "ballId": 2004, "count": 1 } + ] + }, + { + "dropId": 22010037, + "dropList": [ + { "ballId": 2007, "count": 1 } + ] + }, + { + "dropId": 22010032, + "dropList": [ + { "ballId": 2002, "count": 1 } + ] + }, + { + "dropId": 22010022, + "dropList": [ + { "ballId": 2018, "count": 1 } + ] + }, + { + "dropId": 22010036, + "dropList": [ + { "ballId": 2006, "count": 1 } + ] + }, + { + "dropId": 22010026, + "dropList": [ + { "ballId": 2022, "count": 1 } + ] + }, + { + "dropId": 22010031, + "dropList": [ + { "ballId": 2001, "count": 1 } + ] + }, + + { + "dropId": 22010014, + "dropList": [ + { "ballId": 2020, "count": 1 } + ] + }, + { + "dropId": 22010016, + "dropList": [ + { "ballId": 2022, "count": 1 } + ] + }, + { + "dropId": 22010012, + "dropList": [ + { "ballId": 2018, "count": 1 } + ] + }, + { + "dropId": 22010024, + "dropList": [ + { "ballId": 2004, "count": 1 } + ] + }, + { + "dropId": 22010011, + "dropList": [ + { "ballId": 2017, "count": 1 } + ] + }, + { + "dropId": 22010017, + "dropList": [ + { "ballId": 2023, "count": 1 } + ] + }, + { + "dropId": 22010021, + "dropList": [ + { "ballId": 2017, "count": 1 } + ] + }, + { + "dropId": 22010027, + "dropList": [ + { "ballId": 2007, "count": 1 } + ] + }, + { + "dropId": 22010040, + "dropList": [ + { "ballId": 2024, "count": 1 }, + { "ballId": 2008, "count": 1 } + ] + }, + { + "dropId": 22010025, + "dropList": [ + { "ballId": 2021, "count": 2 } + ] + }, + { + "dropId": 22010020, + "dropList": [ + { "ballId": 2024, "count": 1 } + ] + }, + { + "dropId": 22003100, + "dropList": [ + ] + } +] \ No newline at end of file