diff --git a/src/main/java/emu/grasscutter/data/binout/AbilityModifier.java b/src/main/java/emu/grasscutter/data/binout/AbilityModifier.java index 69e89fbe5..c3a12216e 100644 --- a/src/main/java/emu/grasscutter/data/binout/AbilityModifier.java +++ b/src/main/java/emu/grasscutter/data/binout/AbilityModifier.java @@ -327,6 +327,11 @@ public class AbilityModifier implements Serializable { public AbilityModifierAction[] successActions; public AbilityModifierAction[] failActions; + public int dropType; + public DynamicFloat baseEnergy; + public DynamicFloat ratio; + public int configID; + public int param1; public int param2; public int param3; diff --git a/src/main/java/emu/grasscutter/data/binout/config/ConfigLevelEntity.java b/src/main/java/emu/grasscutter/data/binout/config/ConfigLevelEntity.java index 451024fa4..e45cd7d69 100644 --- a/src/main/java/emu/grasscutter/data/binout/config/ConfigLevelEntity.java +++ b/src/main/java/emu/grasscutter/data/binout/config/ConfigLevelEntity.java @@ -1,9 +1,10 @@ package emu.grasscutter.data.binout.config; import emu.grasscutter.data.binout.config.fields.ConfigAbilityData; -import java.util.List; import lombok.Getter; +import java.util.List; + public class ConfigLevelEntity { @Getter private List abilities; @@ -11,4 +12,6 @@ public class ConfigLevelEntity { @Getter private List avatarAbilities; @Getter private List teamAbilities; @Getter private List preloadMonsterEntityIDs; + + @Getter private String dropElemControlType; } diff --git a/src/main/java/emu/grasscutter/game/ability/actions/ActionGenerateElemBall.java b/src/main/java/emu/grasscutter/game/ability/actions/ActionGenerateElemBall.java new file mode 100644 index 000000000..15cfebce4 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/ability/actions/ActionGenerateElemBall.java @@ -0,0 +1,86 @@ +package emu.grasscutter.game.ability.actions; + +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; +import emu.grasscutter.data.binout.config.ConfigLevelEntity; +import emu.grasscutter.game.ability.Ability; +import emu.grasscutter.game.entity.EntityAvatar; +import emu.grasscutter.game.entity.EntityItem; +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.props.ItemUseOp; +import emu.grasscutter.game.props.SceneType; +import emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.AbilityActionGenerateElemBall; +import emu.grasscutter.utils.Position; + +@AbilityAction(AbilityModifierAction.Type.GenerateElemBall) +public class ActionGenerateElemBall extends AbilityActionHandler { + @Override + public boolean execute(Ability ability, AbilityModifierAction action, ByteString abilityData, GameEntity target) { + GameEntity owner = ability.getOwner(); + + AbilityActionGenerateElemBall generateElemBall; + try { + generateElemBall = AbilityActionGenerateElemBall.parseFrom(abilityData); + } catch (InvalidProtocolBufferException e) { + return false; + } + + //Check if we should allow elem ball generation + if(action.dropType == 0x0) { + String levelEntityConfig = owner.getScene().getSceneData().getLevelEntityConfig(); + ConfigLevelEntity config = GameData.getConfigLevelEntityDataMap().get(levelEntityConfig); + if(config != null && config.getDropElemControlType() != null && config.getDropElemControlType().compareTo("None") == 0) { + Grasscutter.getLogger().warn("This level config don't allow element balls"); + return true; + } + } else if(action.dropType == 0x1) { + if(owner.getScene().getSceneData().getSceneType() != SceneType.SCENE_WORLD) { + Grasscutter.getLogger().warn("This level config only allows element balls on big world"); + return true; + } + } //Else the drop is forced + + var energy = action.baseEnergy.get(ability) * action.ratio.get(ability); + if(energy <= 0.0) return true; + + var itemData = GameData.getItemDataMap().get(action.configID); + if(itemData == null) { + Grasscutter.getLogger().warn("configID {} not found", action.configID); + return false; + } + + if(itemData.getItemUse() == null || itemData.getItemUse().isEmpty()) { + Grasscutter.getLogger().warn("Item {} has no item use array", action.configID); + return true; + } + + var itemUse = itemData.getItemUse().get(0); + double requiredEnergy; + switch(itemUse.getUseOp()) { + case ITEM_USE_ADD_ELEM_ENERGY: requiredEnergy = Integer.parseInt(itemUse.getUseParam()[1]); break; + case ITEM_USE_ADD_ALL_ENERGY: requiredEnergy = Integer.parseInt(itemUse.getUseParam()[0]); break; + default: + Grasscutter.getLogger().warn("UseOp not implemented", itemUse.getUseOp()); + return false; + } + + var amountGenerated = (int)Math.ceil(energy / requiredEnergy); + if(amountGenerated >= 21) { + Grasscutter.getLogger().warn("Attempt to generate more than 20 element balls {}", amountGenerated); + return false; + } + + Grasscutter.getLogger().debug("Generating {} of {} element balls", amountGenerated, action.configID); + for(int i = 0; i < amountGenerated; i++) { + EntityItem energyBall = new EntityItem(owner.getScene(), (owner instanceof EntityAvatar avatar) ? avatar.getPlayer() : null, itemData, new Position(generateElemBall.getPos()), new Position(generateElemBall.getRot()), 1); + owner.getScene().addEntity(energyBall); + } + + return true; + } +} + diff --git a/src/main/java/emu/grasscutter/game/entity/EntityItem.java b/src/main/java/emu/grasscutter/game/entity/EntityItem.java index 9a7b7845a..70f2b52d0 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityItem.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityItem.java @@ -37,14 +37,19 @@ public class EntityItem extends EntityBaseGadget { this(scene, player, itemData, pos, count, true); } - // In official game, some drop items are shared to all players, and some other items are - // independent to all players - // For example, if you killed a monster in MP mode, all players could get drops but rarity and - // number of them are different + public EntityItem(Scene scene, Player player, ItemData itemData, Position pos, Position rotation, int count) { + this(scene, player, itemData, pos, rotation, count, true); + } + + public EntityItem(Scene scene, Player player, ItemData itemData, Position pos, int count, boolean share) { + this(scene, player, itemData, pos, null, count, share); + } + + // In official game, some drop items are shared to all players, and some other items are independent to all players + // For example, if you killed a monster in MP mode, all players could get drops but rarity and number of them are different // but if you broke regional mine, when someone picked up the drop then it disappeared - public EntityItem( - Scene scene, Player player, ItemData itemData, Position pos, int count, boolean share) { - super(scene, pos, null); + public EntityItem(Scene scene, Player player, ItemData itemData, Position pos, Position rotation, int count, boolean share) { + super(scene, pos, rotation); this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET); this.guid = player == null ? scene.getWorld().getHost().getNextGameGuid() : player.getNextGameGuid();