22
33import de .erethon .aether .Aether ;
44import de .erethon .aether .ai .goals .AEPathfinderGoal ;
5+ import de .erethon .aether .ai .goals .HurtByTarget ;
6+ import de .erethon .aether .ai .goals .NearestAttackableTarget ;
57import de .erethon .aether .combat .MobAttributeRange ;
68import de .erethon .aether .combat .MobLevelInfo ;
79import de .erethon .aether .combat .MobLevelLoot ;
2224import de .erethon .papyrus .entities .MobSpawnCategoryOverrideProvider ;
2325import de .erethon .questsxl .QuestsXL ;
2426import 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 ;
2530import io .papermc .paper .adventure .PaperAdventure ;
2631import net .kyori .adventure .text .Component ;
2732import net .kyori .adventure .text .format .NamedTextColor ;
3641import net .minecraft .server .level .ServerLevel ;
3742import net .minecraft .sounds .SoundEvents ;
3843import net .minecraft .sounds .SoundSource ;
39- import net .minecraft .world .DifficultyInstance ;
4044import net .minecraft .world .InteractionHand ;
4145import net .minecraft .world .InteractionResult ;
4246import net .minecraft .world .damagesource .DamageSource ;
4953import net .minecraft .world .entity .Mob ;
5054import net .minecraft .world .entity .MobCategory ;
5155import net .minecraft .world .entity .Pose ;
52- import net .minecraft .world .entity .SpawnGroupData ;
5356import net .minecraft .world .entity .ai .attributes .Attribute ;
57+ import net .minecraft .world .entity .ai .attributes .Attributes ;
5458import net .minecraft .world .entity .ai .goal .MeleeAttackGoal ;
5559import net .minecraft .world .entity .ai .goal .RandomLookAroundGoal ;
5660import net .minecraft .world .entity .ai .goal .RandomStrollGoal ;
6872import net .minecraft .world .item .Items ;
6973import net .minecraft .world .item .ProjectileWeaponItem ;
7074import net .minecraft .world .level .Level ;
71- import net .minecraft .world .level .ServerLevelAccessor ;
7275import net .minecraft .world .level .gameevent .GameEvent ;
7376import net .minecraft .world .level .storage .ValueInput ;
7477import net .minecraft .world .level .storage .ValueOutput ;
7578import org .bukkit .Bukkit ;
79+ import org .bukkit .Color ;
7680import org .bukkit .World ;
7781import org .bukkit .craftbukkit .CraftSound ;
7882import 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