package emu.grasscutter.game.entity; import java.util.Optional; import emu.grasscutter.data.GameData; import emu.grasscutter.data.common.PropGrowCurve; import emu.grasscutter.data.excels.EnvAnimalGatherConfigData; import emu.grasscutter.data.excels.MonsterCurveData; import emu.grasscutter.data.excels.MonsterData; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.EntityIdType; import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.WatcherTriggerType; import emu.grasscutter.game.world.Scene; import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo; import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair; import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo; import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData; import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo; import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq; import emu.grasscutter.net.proto.MonsterBornTypeOuterClass.MonsterBornType; import emu.grasscutter.net.proto.PropPairOuterClass.PropPair; import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType; import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo; import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo; import emu.grasscutter.net.proto.SceneMonsterInfoOuterClass.SceneMonsterInfo; import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo; import emu.grasscutter.scripts.constants.EventType; import emu.grasscutter.scripts.data.ScriptArgs; import emu.grasscutter.utils.Position; import emu.grasscutter.utils.ProtoHelper; import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; import lombok.Getter; import lombok.Setter; public class EntityMonster extends GameEntity { @Getter private final MonsterData monsterData; @Getter(onMethod_ = @Override) private final Int2FloatOpenHashMap fightProperties; @Getter(onMethod_ = @Override) private final Position position; @Getter(onMethod_ = @Override) private final Position rotation; @Getter private final Position bornPos; @Getter private final int level; private int weaponEntityId; @Getter @Setter private int poseId; @Getter @Setter private int aiId = -1; public EntityMonster(Scene scene, MonsterData monsterData, Position pos, int level) { super(scene); this.id = getWorld().getNextEntityId(EntityIdType.MONSTER); this.monsterData = monsterData; this.fightProperties = new Int2FloatOpenHashMap(); this.position = new Position(pos); this.rotation = new Position(); this.bornPos = getPosition().clone(); this.level = level; // Monster weapon if (getMonsterWeaponId() > 0) { this.weaponEntityId = getWorld().getNextEntityId(EntityIdType.WEAPON); } this.recalcStats(); } public int getMonsterWeaponId() { return this.getMonsterData().getWeaponId(); } private int getMonsterId() { return this.getMonsterData().getId(); } @Override public boolean isAlive() { return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f; } @Override public void onInteract(Player player, GadgetInteractReq interactReq) { EnvAnimalGatherConfigData gatherData = GameData.getEnvAnimalGatherConfigDataMap().get(this.getMonsterData().getId()); if (gatherData == null) { return; } player.getInventory().addItem(gatherData.getGatherItem(), ActionReason.SubfieldDrop); this.getScene().killEntity(this); } @Override public void onCreate() { // Lua event getScene().getScriptManager().callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(this.getConfigId())); } @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) { super.onDeath(killerId); // Invoke super class's onDeath() method. var scene = this.getScene(); var challenge = Optional.ofNullable(scene.getChallenge()); var scriptManager = scene.getScriptManager(); Optional.ofNullable(this.getSpawnEntry()).ifPresent(scene.getDeadSpawnedEntities()::add); // first set the challenge data challenge.ifPresent(c -> c.onMonsterDeath(this)); if (scriptManager.isInit() && this.getGroupId() > 0) { Optional.ofNullable(scriptManager.getScriptMonsterSpawnService()).ifPresent(s -> s.onMonsterDead(this)); // prevent spawn monster after success if (challenge.map(c -> c.inProgress()).orElse(true)) scriptManager.callEvent(EventType.EVENT_ANY_MONSTER_DIE, new ScriptArgs().setParam1(this.getConfigId())); } // Battle Pass trigger scene.getPlayers().forEach(p -> p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_MONSTER_DIE, this.getMonsterId(), 1)); } public void recalcStats() { // Monster data MonsterData data = this.getMonsterData(); // Get hp percent, set to 100% if none float hpPercent = this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) <= 0 ? 1f : this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) / this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP); // Clear properties this.getFightProperties().clear(); // Base stats MonsterData.definedFightProperties.forEach(prop -> this.setFightProperty(prop, data.getFightProperty(prop))); // Level curve MonsterCurveData curve = GameData.getMonsterCurveDataMap().get(this.getLevel()); if (curve != null) { for (PropGrowCurve growCurve : data.getPropGrowCurves()) { FightProperty prop = FightProperty.getPropByName(growCurve.getType()); this.setFightProperty(prop, this.getFightProperty(prop) * curve.getMultByProp(growCurve.getGrowCurve())); } } // Set % stats FightProperty.forEachCompoundProperty(c -> this.setFightProperty(c.getResult(), this.getFightProperty(c.getFlat()) + (this.getFightProperty(c.getBase()) * (1f + this.getFightProperty(c.getPercent()))))); // Set current hp this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent); } @Override public SceneEntityInfo toProto() { var authority = EntityAuthorityInfo.newBuilder() .setAbilityInfo(AbilitySyncStateInfo.newBuilder()) .setRendererChangedInfo(EntityRendererChangedInfo.newBuilder()) .setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(this.getBornPos().toProto())) .setBornPos(this.getBornPos().toProto()) .build(); var entityInfo = SceneEntityInfo.newBuilder() .setEntityId(getId()) .setEntityType(ProtEntityType.PROT_ENTITY_TYPE_MONSTER) .setMotionInfo(this.getMotionInfo()) .addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder()) .setEntityClientData(EntityClientData.newBuilder()) .setEntityAuthorityInfo(authority) .setLifeState(this.getLifeState().getValue()); this.addAllFightPropsToEntityInfo(entityInfo); entityInfo.addPropList(PropPair.newBuilder() .setType(PlayerProperty.PROP_LEVEL.getId()) .setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getLevel())) .build()); var monsterInfo = SceneMonsterInfo.newBuilder() .setMonsterId(getMonsterId()) .setGroupId(this.getGroupId()) .setConfigId(this.getConfigId()) .addAllAffixList(getMonsterData().getAffix()) .setAuthorityPeerId(getWorld().getHostPeerId()) .setPoseId(this.getPoseId()) .setBlockId(3001) .setBornType(MonsterBornType.MONSTER_BORN_TYPE_DEFAULT) .setSpecialNameId(40); if (getMonsterData().getDescribeData() != null) { monsterInfo.setTitleId(getMonsterData().getDescribeData().getTitleID()); } if (this.getMonsterWeaponId() > 0) { SceneWeaponInfo weaponInfo = SceneWeaponInfo.newBuilder() .setEntityId(this.weaponEntityId) .setGadgetId(this.getMonsterWeaponId()) .setAbilityInfo(AbilitySyncStateInfo.newBuilder()) .build(); monsterInfo.addWeaponList(weaponInfo); } if (this.aiId!=-1) { monsterInfo.setAiConfigId(aiId); } entityInfo.setMonster(monsterInfo); return entityInfo.build(); } }