mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-12-14 07:55:57 +01:00
Handle mob summon and limbo state (#2432)
Mob summon: Something like Monster_Apparatus_Perpetual can summon helper mobs. Ensure these helpers actually get summoned and, on their defeat, possibly change the summoner's mob state. Like, temporarily enter weak state. * Take summon tags from BinOutput/Monster/ConfigMonster_*.json and put them in SceneMonsterInfo * Handle Summon action in ability modifiers from BinOutput/Ability/Temp/MonsterAbilities/ConfigAbility_Monster_*.json * On summoner's kill, also kill the summoned mobs Limbo state: Something like Monster_Invoker_Herald_Water should be invulnerable at a certain HP threshold. Like, shouldn't die when creating their elemental shield. Or, Monster_Apparatus_Perpetual's helper mobs shouldn't die before their summoner. * Look through ConfigAbility (AbilityData in GC) like Invoker_Herald_Water_StateControl. If any AbilityModifier within specifies state Limbo and properties.Actor_HpThresholdRatio, account for this threshold in GameEntity::damage. * Don't let the entity die while in limbo. They will be killed by other events.
This commit is contained in:
@@ -25,6 +25,7 @@ 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.net.proto.ServantInfoOuterClass.ServantInfo;
|
||||
import emu.grasscutter.scripts.constants.EventType;
|
||||
import emu.grasscutter.scripts.data.*;
|
||||
import emu.grasscutter.server.event.entity.EntityDamageEvent;
|
||||
@@ -49,6 +50,9 @@ public class EntityMonster extends GameEntity {
|
||||
@Getter private final Position bornPos;
|
||||
@Getter private final int level;
|
||||
@Getter private EntityWeapon weaponEntity;
|
||||
@Getter private Map<Integer, EntityMonster> summonTagMap;
|
||||
@Getter @Setter private int summonedTag;
|
||||
@Getter @Setter private int ownerEntityId;
|
||||
@Getter @Setter private int poseId;
|
||||
@Getter @Setter private int aiId = -1;
|
||||
|
||||
@@ -67,6 +71,9 @@ public class EntityMonster extends GameEntity {
|
||||
this.bornPos = this.getPosition().clone();
|
||||
this.level = level;
|
||||
this.playerOnBattle = new ArrayList<>();
|
||||
this.summonTagMap = new HashMap<>();
|
||||
this.summonedTag = 0;
|
||||
this.ownerEntityId = 0;
|
||||
|
||||
if (GameData.getMonsterMappingMap().containsKey(this.getMonsterId())) {
|
||||
this.configEntityMonster =
|
||||
@@ -76,6 +83,14 @@ public class EntityMonster extends GameEntity {
|
||||
this.configEntityMonster = null;
|
||||
}
|
||||
|
||||
if (this.configEntityMonster != null &&
|
||||
this.configEntityMonster.getCombat() != null &&
|
||||
this.configEntityMonster.getCombat().getSummon() != null &&
|
||||
this.configEntityMonster.getCombat().getSummon().getSummonTags() != null) {
|
||||
this.configEntityMonster.getCombat().getSummon().getSummonTags().forEach(
|
||||
t -> this.summonTagMap.put(t.getSummonTag(), null));
|
||||
}
|
||||
|
||||
// Monster weapon
|
||||
if (getMonsterWeaponId() > 0) {
|
||||
this.weaponEntity = new EntityWeapon(scene, getMonsterWeaponId());
|
||||
@@ -316,6 +331,11 @@ public class EntityMonster extends GameEntity {
|
||||
this.getMonsterData().getType().getValue());
|
||||
scene.triggerDungeonEvent(
|
||||
DungeonPassConditionType.DUNGEON_COND_KILL_MONSTER, this.getMonsterId());
|
||||
|
||||
// If this entity spawned servants, kill those too.
|
||||
summonTagMap.values().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(entity -> scene.killEntity(entity, killerId));
|
||||
}
|
||||
|
||||
public void recalcStats() {
|
||||
@@ -387,14 +407,21 @@ public class EntityMonster extends GameEntity {
|
||||
public SceneEntityInfo toProto() {
|
||||
var data = this.getMonsterData();
|
||||
|
||||
var aiInfo =
|
||||
SceneEntityAiInfo.newBuilder()
|
||||
.setIsAiOpen(true)
|
||||
.setBornPos(this.getBornPos().toProto());
|
||||
if (ownerEntityId != 0) {
|
||||
aiInfo.setServantInfo(
|
||||
ServantInfo.newBuilder()
|
||||
.setMasterEntityId(ownerEntityId));
|
||||
}
|
||||
|
||||
var authority =
|
||||
EntityAuthorityInfo.newBuilder()
|
||||
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
|
||||
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
|
||||
.setAiInfo(
|
||||
SceneEntityAiInfo.newBuilder()
|
||||
.setIsAiOpen(true)
|
||||
.setBornPos(this.getBornPos().toProto()))
|
||||
.setAiInfo(aiInfo)
|
||||
.setBornPos(this.getBornPos().toProto())
|
||||
.build();
|
||||
|
||||
@@ -425,7 +452,10 @@ public class EntityMonster extends GameEntity {
|
||||
.setAuthorityPeerId(this.getWorld().getHostPeerId())
|
||||
.setPoseId(this.getPoseId())
|
||||
.setBlockId(this.getScene().getId())
|
||||
.setSummonedTag(this.summonedTag)
|
||||
.setOwnerEntityId(this.ownerEntityId)
|
||||
.setBornType(MonsterBornType.MONSTER_BORN_TYPE_DEFAULT);
|
||||
summonTagMap.forEach((k, v) -> monsterInfo.putSummonTagMap(k, v == null ? 0 : 1));
|
||||
|
||||
if (this.metaMonster != null) {
|
||||
if (this.metaMonster.special_name_id != 0) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package emu.grasscutter.game.entity;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.binout.*;
|
||||
import emu.grasscutter.game.ability.*;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.props.*;
|
||||
@@ -32,6 +34,8 @@ public abstract class GameEntity {
|
||||
@Getter @Setter private int lastMoveReliableSeq;
|
||||
|
||||
@Getter @Setter private boolean lockHP;
|
||||
private boolean limbo;
|
||||
private float limboHpThreshold;
|
||||
|
||||
@Setter(AccessLevel.PROTECTED)
|
||||
@Getter
|
||||
@@ -110,6 +114,20 @@ public abstract class GameEntity {
|
||||
});
|
||||
}
|
||||
|
||||
protected void setLimbo(float hpThreshold) {
|
||||
limbo = true;
|
||||
limboHpThreshold = hpThreshold;
|
||||
}
|
||||
|
||||
public void onAddAbilityModifier(AbilityModifier data) {
|
||||
// Set limbo state (invulnerability at a certain HP threshold)
|
||||
// if ability modifier calls for it
|
||||
if (data.state == AbilityModifier.State.Limbo &&
|
||||
data.properties != null && data.properties.Actor_HpThresholdRatio > .0f) {
|
||||
this.setLimbo(data.properties.Actor_HpThresholdRatio);
|
||||
}
|
||||
}
|
||||
|
||||
protected MotionInfo getMotionInfo() {
|
||||
return MotionInfo.newBuilder()
|
||||
.setPos(this.getPosition().toProto())
|
||||
@@ -167,11 +185,26 @@ public abstract class GameEntity {
|
||||
return; // If the event is canceled, do not damage the entity.
|
||||
}
|
||||
|
||||
float effectiveDamage = 0;
|
||||
float curHp = getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
||||
if (curHp != Float.POSITIVE_INFINITY && !lockHP || lockHP && curHp <= event.getDamage()) {
|
||||
// Add negative HP to the current HP property.
|
||||
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -(event.getDamage()));
|
||||
if (limbo) {
|
||||
float maxHp = getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
|
||||
float curRatio = curHp / maxHp;
|
||||
if (curRatio > limboHpThreshold) {
|
||||
// OK if this hit takes HP below threshold.
|
||||
effectiveDamage = event.getDamage();
|
||||
}
|
||||
if (effectiveDamage >= curHp && limboHpThreshold > .0f) {
|
||||
// Don't let entity die while in limbo.
|
||||
effectiveDamage = curHp - 1;
|
||||
}
|
||||
}
|
||||
else if (curHp != Float.POSITIVE_INFINITY && !lockHP || lockHP && curHp <= event.getDamage()) {
|
||||
effectiveDamage = event.getDamage();
|
||||
}
|
||||
|
||||
// Add negative HP to the current HP property.
|
||||
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -effectiveDamage);
|
||||
|
||||
this.lastAttackType = attackType;
|
||||
this.checkIfDead();
|
||||
|
||||
Reference in New Issue
Block a user