Skip to content

Commit 15727b6

Browse files
committed
Fix some NPC bugs
1 parent b1a8741 commit 15727b6

File tree

12 files changed

+385
-23
lines changed

12 files changed

+385
-23
lines changed

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ dependencies {
3030
compileOnly("de.erethon", "Daedalus", "1.2")
3131

3232
compileOnly("de.erethon.hephaestus:Hephaestus:1.0.4-SNAPSHOT")
33-
compileOnly("de.erethon.questsxl:QuestsXL:1.0.4-SNAPSHOT")
33+
compileOnly("de.erethon.questsxl:QuestsXL:1.0.5-SNAPSHOT")
3434
compileOnly("de.erethon.hecate:Hecate:1.2-SNAPSHOT")
3535
compileOnly("de.erethon.factions:Factions:1.0-SNAPSHOT")
3636
}

src/main/java/de/erethon/aether/Aether.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
import de.erethon.aether.creature.SkinCache;
77
import de.erethon.aether.listener.EntityListener;
88
import de.erethon.aether.listener.PlayerListener;
9+
import de.erethon.aether.qxl.actions.MobWalkAction;
910
import de.erethon.aether.qxl.actions.SpawnMobAction;
1011
import de.erethon.aether.qxl.actions.SpawnerAction;
12+
import de.erethon.aether.qxl.objectives.InteractMobObjective;
1113
import de.erethon.aether.qxl.objectives.KillMobObjective;
1214
import de.erethon.aether.spawning.MobSpawnConfig;
1315
import de.erethon.aether.spawning.NaturalSpawningListener;
@@ -24,6 +26,7 @@
2426
import de.erethon.questsxl.common.QRegistries;
2527
import net.kyori.adventure.text.Component;
2628
import net.kyori.adventure.text.format.NamedTextColor;
29+
import net.minecraft.world.entity.EntityType;
2730
import org.bukkit.Bukkit;
2831
import org.bukkit.NamespacedKey;
2932
import org.bukkit.entity.Player;
@@ -40,6 +43,7 @@
4043
import java.util.ArrayList;
4144
import java.util.List;
4245
import java.util.Locale;
46+
import java.util.Map;
4347

4448
public final class Aether extends EPlugin implements Listener {
4549

@@ -94,8 +98,14 @@ public static void addException(String locationIdentifier, String errorMessage,
9498

9599
@Override
96100
public void onLoad() {
101+
instance = this;
97102
hephaestusPlugin = (Hephaestus) Bukkit.getPluginManager().getPlugin("Hephaestus");
98103
hitemLibrary = hephaestusPlugin.getLibrary();
104+
CREATURES = new File(getDataFolder(), "creatures");
105+
if (!CREATURES.exists()) {
106+
CREATURES.mkdir();
107+
}
108+
CreatureManager.loadEarly(CREATURES); // We need to register class mappings before the world is loaded
99109
}
100110

101111
@Override
@@ -106,7 +116,6 @@ public void onEnable() {
106116
Bukkit.getPluginManager().disablePlugin(this);
107117
return;
108118
}
109-
instance = this;
110119

111120
if (!getDataFolder().exists()) {
112121
getDataFolder().mkdir();
@@ -117,10 +126,6 @@ public void onEnable() {
117126
MODELS.mkdir();
118127
}
119128

120-
CREATURES = new File(getDataFolder(), "creatures");
121-
if (!CREATURES.exists()) {
122-
CREATURES.mkdir();
123-
}
124129
SPAWNERS = new File(getDataFolder(), "spawners");
125130
if (!SPAWNERS.exists()) {
126131
SPAWNERS.mkdir();
@@ -158,7 +163,9 @@ public void onEnable() {
158163
if (questsXL != null) {
159164
questsXL.registerComponent(QRegistries.ACTIONS, "spawn_mob", SpawnMobAction::new);
160165
questsXL.registerComponent(QRegistries.ACTIONS, "spawner", SpawnerAction::new);
166+
questsXL.registerComponent(QRegistries.ACTIONS, "mob_walk", MobWalkAction::new);
161167
questsXL.registerComponent(QRegistries.OBJECTIVES, "kill_mob", KillMobObjective::new);
168+
questsXL.registerComponent(QRegistries.OBJECTIVES, "interact_mob", InteractMobObjective::new);
162169
}
163170
}
164171

src/main/java/de/erethon/aether/ai/goals/NearestAttackableTarget.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,12 @@ public void load(String[] args) {
4343
}
4444
checkVisibility = Boolean.parseBoolean(args[1]);
4545
}
46+
47+
public Class getTarget() {
48+
return target;
49+
}
50+
51+
public boolean isCheckVisibility() {
52+
return checkVisibility;
53+
}
4654
}

src/main/java/de/erethon/aether/commands/TestCommand.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.bukkit.entity.*;
2020
import org.bukkit.event.Listener;
2121
import org.bukkit.inventory.ItemStack;
22+
import org.bukkit.map.MinecraftFont;
2223
import org.bukkit.util.BoundingBox;
2324

2425
import java.util.HashMap;

src/main/java/de/erethon/aether/creature/AetherBaseMob.java

Lines changed: 112 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import de.erethon.aether.Aether;
44
import de.erethon.aether.ai.goals.AEPathfinderGoal;
5+
import de.erethon.aether.ai.goals.HurtByTarget;
6+
import de.erethon.aether.ai.goals.NearestAttackableTarget;
57
import de.erethon.aether.combat.MobAttributeRange;
68
import de.erethon.aether.combat.MobLevelInfo;
79
import de.erethon.aether.combat.MobLevelLoot;
@@ -22,6 +24,9 @@
2224
import de.erethon.papyrus.entities.MobSpawnCategoryOverrideProvider;
2325
import de.erethon.questsxl.QuestsXL;
2426
import de.erethon.questsxl.player.QPlayer;
27+
import de.erethon.spellbook.Spellbook;
28+
import de.erethon.spellbook.teams.SpellbookTeam;
29+
import de.erethon.spellbook.teams.TeamManager;
2530
import io.papermc.paper.adventure.PaperAdventure;
2631
import net.kyori.adventure.text.Component;
2732
import net.kyori.adventure.text.format.NamedTextColor;
@@ -36,7 +41,6 @@
3641
import net.minecraft.server.level.ServerLevel;
3742
import net.minecraft.sounds.SoundEvents;
3843
import net.minecraft.sounds.SoundSource;
39-
import net.minecraft.world.DifficultyInstance;
4044
import net.minecraft.world.InteractionHand;
4145
import net.minecraft.world.InteractionResult;
4246
import net.minecraft.world.damagesource.DamageSource;
@@ -49,8 +53,8 @@
4953
import net.minecraft.world.entity.Mob;
5054
import net.minecraft.world.entity.MobCategory;
5155
import net.minecraft.world.entity.Pose;
52-
import net.minecraft.world.entity.SpawnGroupData;
5356
import net.minecraft.world.entity.ai.attributes.Attribute;
57+
import net.minecraft.world.entity.ai.attributes.Attributes;
5458
import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
5559
import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
5660
import net.minecraft.world.entity.ai.goal.RandomStrollGoal;
@@ -68,11 +72,11 @@
6872
import net.minecraft.world.item.Items;
6973
import net.minecraft.world.item.ProjectileWeaponItem;
7074
import net.minecraft.world.level.Level;
71-
import net.minecraft.world.level.ServerLevelAccessor;
7275
import net.minecraft.world.level.gameevent.GameEvent;
7376
import net.minecraft.world.level.storage.ValueInput;
7477
import net.minecraft.world.level.storage.ValueOutput;
7578
import org.bukkit.Bukkit;
79+
import org.bukkit.Color;
7680
import org.bukkit.World;
7781
import org.bukkit.craftbukkit.CraftSound;
7882
import org.bukkit.craftbukkit.CraftWorld;
@@ -98,6 +102,7 @@ public class AetherBaseMob extends Monster implements RangedAttackMob, CrossbowA
98102
private int version = 0;
99103
protected CraftCustomMob bukkitLivingEntity;
100104
private AetherHolder holder;
105+
private String mobTag = null;
101106

102107
private int mobLevel = 1;
103108
private MobLevelInfo levelInfo;
@@ -107,10 +112,10 @@ public class AetherBaseMob extends Monster implements RangedAttackMob, CrossbowA
107112

108113
private Map<Player, Double> damageTracker = new HashMap<>();
109114

110-
111115
// Constructor for entity loading
112116
public AetherBaseMob(EntityType<? extends Mob> type, Level world) {
113117
super((EntityType<? extends Monster>) type, world);
118+
Aether.log("Entity loading constructor called for " + type);
114119
}
115120

116121
public AetherBaseMob(NPCData data, World world) {
@@ -133,6 +138,7 @@ public AetherBaseMob(NPCData data, World world, Integer overrideLevel) {
133138
bukkitLivingEntity.setType(data.getDisplayType());
134139
entityData = DataMappings.getSynchedEntityData(data.getDisplayType());
135140
version = data.getCurrentVersion();
141+
Aether.log("Loading Aether mob " + data.getID());
136142
onLoad();
137143
onFirstSpawn();
138144
}
@@ -145,6 +151,8 @@ public void addToWorld() {
145151
@Override
146152
public void tick() {
147153
super.tick();
154+
getBukkitLivingEntity().setMaxEnergy(100);
155+
getBukkitLivingEntity().setEnergy(100); // Mobs have infinite energy
148156
}
149157

150158
private void logDebug(String message) {
@@ -258,6 +266,9 @@ public boolean hurtServer(ServerLevel level, DamageSource source, float amount,
258266
Aether.log("Failed to handle damage for " + data.getID() + ": " + e.getMessage());
259267
}
260268
damageTracker.put(player, damageTracker.getOrDefault(player, 0.0) + amount);
269+
if (data.getFaction() != null && !Spellbook.canAttack(player.getBukkitEntity(), getBukkitLivingEntity())) {
270+
return false;
271+
}
261272
}
262273
return super.hurtServer(level, source, amount, type);
263274
}
@@ -332,6 +343,9 @@ public void die(DamageSource damageSource) {
332343
if (holder != null) {
333344
holder.onDeath();
334345
}
346+
if (mobTag != null) {
347+
plugin.getCreatureManager().removeTaggedMob(mobTag);
348+
}
335349
}
336350

337351
private void distributeLootAndXP() {
@@ -556,33 +570,76 @@ public void run() {
556570
removeStand.runTaskLater(plugin, timeout * 20L);
557571
}
558572

573+
public void setMobTag(String tag) {
574+
if (mobTag != null && !mobTag.equals(tag)) {
575+
plugin.getCreatureManager().removeTaggedMob(mobTag);
576+
}
577+
this.mobTag = tag;
578+
plugin.getCreatureManager().addTaggedMob(tag, this);
579+
}
580+
581+
public String getMobTag() {
582+
return mobTag;
583+
}
584+
585+
public void setFaction(String faction) {
586+
TeamManager teamManager = Spellbook.getInstance().getTeamManager();
587+
if (data.getFaction() != null) {
588+
SpellbookTeam team = teamManager.getTeam(data.getFaction());
589+
if (team == null) {
590+
teamManager.createTeam(data.getFaction(), data.getFaction(), Color.RED);
591+
team = teamManager.getTeam(data.getFaction());
592+
}
593+
teamManager.setTeam(getBukkitLivingEntity(), team);
594+
}
595+
}
596+
559597
private void registerAetherGoals() {
560598
if (data.getGoals().isEmpty() && data.getTargets().isEmpty()) {
561599
goalSelector.addGoal(0, new RandomStrollGoal(this, 1));
562600
goalSelector.addGoal(1, new RandomLookAroundGoal(this));
563601
goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.4, false));
564-
targetSelector.addGoal(0, new HurtByTargetGoal(this));
565-
targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, true));
602+
targetSelector.addGoal(0, new HurtByTargetGoal(this, this.getClass()));
603+
if (data.getFaction() == null) {
604+
targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, null));
605+
return;
606+
}
607+
targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 10, false, false, this::testSpellbookTeam));
566608
return;
567609
}
568610
for (AEPathfinderGoal aeGoal : data.getGoals()) {
569611
goalSelector.addGoal(aeGoal.getPrio(), aeGoal.get(this));
570612
}
571613
if (data.getTargets().isEmpty()) {
572-
targetSelector.addGoal(0, new HurtByTargetGoal(this));
573-
targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, true));
614+
targetSelector.addGoal(0, new HurtByTargetGoal(this, this.getClass()));
615+
if (data.getFaction() == null) {
616+
targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, null));
617+
return;
618+
}
619+
targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 10, false, false, this::testSpellbookTeam));
574620
return;
575621
}
576622
for (AEPathfinderGoal aeGoal : data.getTargets()) {
623+
if (aeGoal instanceof NearestAttackableTarget nearestAttackableTarget) {
624+
targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, nearestAttackableTarget.getTarget(), 10, nearestAttackableTarget.isCheckVisibility(), false, this::testSpellbookTeam));
625+
continue; // Always add the spellbook team test
626+
}
627+
if (aeGoal instanceof HurtByTarget) {
628+
targetSelector.addGoal(aeGoal.getPrio(), new HurtByTargetGoal(this, this.getClass()));
629+
continue; // Always add the class exclusion
630+
}
577631
targetSelector.addGoal(aeGoal.getPrio(), aeGoal.get(this));
578632
}
579633
}
580634

581635
@Override
582636
public void readAdditionalSaveData(ValueInput input) {
583637
super.readAdditionalSaveData(input);
638+
Aether.log("Reading additional save data for entity " + this);
584639
Optional<String> papyrusId = input.getString("papyrus-entity-id");
585640
if (papyrusId.isEmpty()) {
641+
Aether.log("Failed to load entity data: No papyrus ID found for entity " + this);
642+
remove(RemovalReason.DISCARDED);
586643
return;
587644
}
588645
data = plugin.getCreatureManager().getByID(papyrusId.get());
@@ -601,6 +658,10 @@ public void readAdditionalSaveData(ValueInput input) {
601658
levelInfo = data.getCompositeLevelInfoForLevel(mobLevel);
602659
Aether.log("Loaded " + data.getID() + " at level " + mobLevel);
603660
}
661+
Optional<String> savedTag = input.getString("aether-mob-tag");
662+
savedTag.ifPresent(this::setMobTag);
663+
Optional<String> savedFaction = input.getString("aether-mob-faction");
664+
savedFaction.ifPresent(this::setFaction);
604665

605666
bukkitLivingEntity = new CraftCustomMob(MinecraftServer.getServer().server, this);
606667
bukkitLivingEntity.setHandle(this);
@@ -619,11 +680,38 @@ public void addAdditionalSaveData(ValueOutput output) {
619680
output.putString("papyrus-entity-id", data.getID());
620681
output.putInt("aether-mob-version", version);
621682
output.putInt("aether-mob-level", mobLevel);
683+
if (mobTag != null) {
684+
output.putString("aether-mob-tag", mobTag);
685+
plugin.getCreatureManager().addTaggedMob(mobTag, this);
686+
}
687+
if (data.getFaction() != null) {
688+
output.putString("aether-mob-faction", data.getFaction());
689+
Spellbook.getInstance().getTeamManager().removeEntityFromTeam(getBukkitLivingEntity());
690+
}
691+
Aether.log("Saved custom entity with class " + this.getClass().getSimpleName() + " and ID " + data.getID());
692+
}
693+
694+
@Override
695+
public boolean shouldBeSaved() {
696+
return data.isPersistent();
697+
}
698+
699+
@Override
700+
public boolean saveAsPassenger(ValueOutput output, boolean includeAll, boolean includeNonSaveable, boolean forceSerialization) {
701+
// Add debug logging
702+
Aether.log("saveAsPassenger called - removalReason: " + this.getRemovalReason() + " shouldSave: " + this.shouldBeSaved() + " isPersist: " + this.isPersistenceRequired());
703+
704+
return super.saveAsPassenger(output, includeAll, includeNonSaveable, forceSerialization);
705+
}
706+
707+
@Override
708+
public boolean save(ValueOutput output) {
709+
Aether.log("save() called");
710+
addAdditionalSaveData(output);
711+
return super.save(output);
622712
}
623713

624714
private void onLoad() {
625-
ServerLevel level = (ServerLevel) level();
626-
level.chunkSource.removeEntity(this);
627715
if (data.getQXLSection() != null) {
628716
holder = AetherHolder.loadFromConfigSection(data.getQXLSection(), this);
629717
if (holder != null) {
@@ -661,8 +749,10 @@ protected void onFirstSpawn() {
661749
setNoGravity(!data.isGravity());
662750
setInvulnerable(data.isInvulnerable());
663751
setPersistenceRequired(data.isPersistent());
752+
persist = data.isPersistent();
664753
collides = data.hasCollision();
665754
maxAirTicks = data.getMaximumAir();
755+
getAttribute(Attributes.COMBAT_HURTINVULNERABILITY).setBaseValue(10); // 0.5 second of invulnerability after being hit by default
666756

667757
applyLevelAttributes();
668758

@@ -762,5 +852,16 @@ private void applyLevelAttributes() {
762852
}
763853
}
764854

855+
public boolean testSpellbookTeam(LivingEntity target, ServerLevel level) {
856+
if (target instanceof AetherBaseMob mob) {
857+
if (mob.data.getFaction() == null) {
858+
return true;
859+
}
860+
if (mob.getData() == this.getData()) {
861+
return false; // Same mob type, don't attack
862+
}
863+
return !Spellbook.canAttack(this.getBukkitLivingEntity(), mob.getBukkitLivingEntity());
864+
}
865+
return true;
866+
}
765867
}
766-

0 commit comments

Comments
 (0)