mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-12-14 16:04:40 +01:00
Fix javadoc & suppress no comments warning Fix terminal interaction in IDEA Update lombok onMethod to JDK8 version Some code reformat
231 lines
9.4 KiB
Java
231 lines
9.4 KiB
Java
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();
|
|
}
|
|
}
|