Jump to content

[1.7.10] Keeping track of mob death/despawn


AndrewFM

Recommended Posts

The thing I'm trying to work on is a custom mob spawner that has a strict limit on the number of monsters it has spawned into the world at any one time. For example, if the limit is set as 2, then after spawning two monsters, it cannot spawn any more until one of those two monsters are killed or despawned. My idea is to give the spawner a data structure that stores references to the spawned entities, easy enough. But then, once they are no longer in the game world, the spawner has to remove them from the reference list. That part seems potentially tricky.

 

A couple ideas I have:

 

1. Call isEntityAlive() every tick, and remove the mob when the function returns false. However, since the entity is destroyed exactly one tick after being killed, I can foresee the following happening if the execution order does not work out in my favor: (isEntityAlive -> yes), (entity killed), (end of tick), (start of next tick), (entity destroyed), (isEntityAlive -> nullPointerException). Also, I'd like to avoid looping through the list of entity references every tick.

 

2. Give each entity a custom IAttribute that stores a reference to the spawner that spawned it, then use that to tell the spawner to remove the mob during a LivingDeathEvent. This seems like a potentially good method, but I don't know if an IAttribute can store an object reference, and I don't know if LivingDeathEvent is called when an entity is "killed" by being naturally despawned.

 

 

I'm quite new to forge modding, so there's likely better solutions. For all of you that know a lot more about the available functionality... what would be your recommended method for going about this problem?

Link to comment
Share on other sites

Haha, whoops, I forgot you can just use world coordinates to grab a block object. Thanks! Only issue I've run into now is that if I want the spawner to be persistent across game sessions, I can't use raw Java object references to keep track of the spawned entities. I need to use something that can be saved/loaded from NBT...

 

Do entities have unique per-instance ids, and is there a function to find an entity object in the world by that id?

 

Edit: found it... it's getEntityID() in Entity, and getEntityByID() in World

Link to comment
Share on other sites

uuid

 

I had a need for something similarm with an NPC spawner block I have.  Assuming the entities that are spawning are custom (if not make them so with a simple extend or implement something in extendedproperties for them), just set them to die on a restart and avoid the whole issue.

Long time Bukkit & Forge Programmer

Happy to try and help

Link to comment
Share on other sites

Okay, I finally got it all working. Delpi is right, UUID has to be used (particularly the getPersistentID() function in Entity). I tried using the ids from getEntityID() and quickly found that they do not get saved with the entities. Those ids also get arbitrarily reassigned every session, so that does not work for this purpose at all.

 

The only annoying thing is there doesn't seem to be a parallel function to getEntityByID() for the UUIDs, so the method I wound up using was to iterate through the "loadedEntityList" in World until I found an entity with a matching UUID. That was a bit annoying to do, because loadedEntityList is initially null when the world session starts, and gets populated as the chunks are loaded. So I had to implement it in such a way so that it would wait a few seconds after readFromNBT was called, before actually going and searching for the UUIDs to populate its reference list.

 

(Fyi to anyone trying to implement something similar... since iterating through the entity list can be an expensive operation, I tried to minimize the amount of times that I had to use it by only using the UUIDs when saving/loading from NBT. I used the entity IDs from getEntityID() anywhere else, such as in the spawner's local reference list)

Link to comment
Share on other sites

I had several people point out to me that worrying about that was over optimizing.  After playing around with it, I tend to agree with them.  I'm doing multiple searches like that per tick and I tried to detect the difference in load and I can't.

 

 

Long time Bukkit & Forge Programmer

Happy to try and help

Link to comment
Share on other sites

  • 2 weeks later...

Okay, I'm still having problems with this.

 

1. Call isEntityAlive() every tick, and remove the mob when the function returns false.

 

I tried a variant of this, where I loop through the loadedEntityList, and compare the UUIDs of the loaded entities against the UUIDs of the entities in the spawner's list. If any of the UUIDs in the spawner are missing, they get removed from the list (in the assumption that that monster is no longer in the world -- either killed or despawned). This assumption doesn't really work, though. If the monster wanders too far away from the spawner, it's possible for the spawner to be in a chunk that is loaded, while the monster is in a chunk that is not loaded. In that case, the spawner incorrectly determines that the monster no longer exists.

 

2. Give each entity a custom IAttribute that stores a reference to the spawner that spawned it, then use that to tell the spawner to remove the mob during a LivingDeathEvent.

 

I couldn't get this method to work either, because LivingDeathEvent does not get called when an entity is despawned. It only gets called when the entity's HP reaches 0. I can't find any forge events that are called upon an entity despawn, either.

Link to comment
Share on other sites

I haven't tried implementing it yet, but I'm not sure if that would work (please correct me if I'm wrong).

 

Let's say the spawner spawns an entity, and it gets added as a weak reference. Then if I traveled far enough away that the chunk the entity was in gets unloaded, wouldn't the entity be unloaded from memory as well? In that case, garbage collection will kick in, killing the weak reference, and causing it to get removed from the hash map. Then if I traveled back to that chunk, and the entity was still there (gets reloaded from file)... the spawner wouldn't have a reference to that entity anymore, and wouldn't acknowledge its existence, right?

 

I think there is a forge event for chunk unloading, though... perhaps on a chunk unload, I could use that to force Minecraft to kill all entities in the chunk that don't have persistence enabled?

Link to comment
Share on other sites

I wound up doing the chunk thing. When I spawn the monsters, I mark them as being custom spawned, and then on a chunk load, I remove any entities in the chunk that are marked as having been spawned by a spawner. I guess this is basically like what delpi suggested... avoiding the whole issue by just killing everything and starting from a clean slate each time.

 

This works sufficiently well for my purposes. It's still not the perfectly ideal solution... so if anyone has a better suggestion, I'm still open to ideas. But I'm pretty happy with this.

Link to comment
Share on other sites

I'll give you one other idea though it will cause you such headache, I'd stick with what you did.

 

If you keep track of the spawner in the entity (nbt) and the uuid in the tile (nbt) then:

- You can force the entity to communicate to the tile and see if still on list.  If not, die.

- tile checks to see if its entities exist on some loop, and deletes if not found

-  There will be times if the thing wanders enough that the tile is loaded and the entity is not or vice versa.  If you write the tile entity code check right, the nbt should load and all should be fine in that direction.  However, if the tile is loaded and the entity isn't, then the above stuff would have marked the entity dead and taken off the list.

 

Of course you could also wait a really long time to cull your list and instead respawn if the found alive entities on the list where still around, ect.  There are a bunch of different ways to skin this, but they are all lacking in some way due to there being no way to make sure the entity chunk and the tile chunk are loaded.

Long time Bukkit & Forge Programmer

Happy to try and help

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • Update your AMD/ATI drivers - get the drivers from their website - do not update via system  
    • As the title says i keep on crashing on forge 1.20.1 even without any mods downloaded, i have the latest drivers (nvidia) and vanilla minecraft works perfectly fine for me logs: https://pastebin.com/5UR01yG9
    • Hello everyone, I'm making this post to seek help for my modded block, It's a special block called FrozenBlock supposed to take the place of an old block, then after a set amount of ticks, it's supposed to revert its Block State, Entity, data... to the old block like this :  The problem I have is that the system breaks when handling multi blocks (I tried some fix but none of them worked) :  The bug I have identified is that the function "setOldBlockFields" in the item's "setFrozenBlock" function gets called once for the 1st block of multiblock getting frozen (as it should), but gets called a second time BEFORE creating the first FrozenBlock with the data of the 1st block, hence giving the same data to the two FrozenBlock :   Old Block Fields set BlockState : Block{minecraft:black_bed}[facing=east,occupied=false,part=head] BlockEntity : net.minecraft.world.level.block.entity.BedBlockEntity@73681674 BlockEntityData : id:"minecraft:bed",x:3,y:-60,z:-6} Old Block Fields set BlockState : Block{minecraft:black_bed}[facing=east,occupied=false,part=foot] BlockEntity : net.minecraft.world.level.block.entity.BedBlockEntity@6d1aa3da BlockEntityData : {id:"minecraft:bed",x:2,y:-60,z:-6} Frozen Block Entity set BlockState : Block{minecraft:black_bed}[facing=east,occupied=false,part=foot] BlockPos{x=3, y=-60, z=-6} BlockEntity : net.minecraft.world.level.block.entity.BedBlockEntity@6d1aa3da BlockEntityData : {id:"minecraft:bed",x:2,y:-60,z:-6} Frozen Block Entity set BlockState : Block{minecraft:black_bed}[facing=east,occupied=false,part=foot] BlockPos{x=2, y=-60, z=-6} BlockEntity : net.minecraft.world.level.block.entity.BedBlockEntity@6d1aa3da BlockEntityData : {id:"minecraft:bed",x:2,y:-60,z:-6} here is the code inside my custom "freeze" item :    @Override     public @NotNull InteractionResult useOn(@NotNull UseOnContext pContext) {         if (!pContext.getLevel().isClientSide() && pContext.getHand() == InteractionHand.MAIN_HAND) {             BlockPos blockPos = pContext.getClickedPos();             BlockPos secondBlockPos = getMultiblockPos(blockPos, pContext.getLevel().getBlockState(blockPos));             if (secondBlockPos != null) {                 createFrozenBlock(pContext, secondBlockPos);             }             createFrozenBlock(pContext, blockPos);             return InteractionResult.SUCCESS;         }         return super.useOn(pContext);     }     public static void createFrozenBlock(UseOnContext pContext, BlockPos blockPos) {         BlockState oldState = pContext.getLevel().getBlockState(blockPos);         BlockEntity oldBlockEntity = oldState.hasBlockEntity() ? pContext.getLevel().getBlockEntity(blockPos) : null;         CompoundTag oldBlockEntityData = oldState.hasBlockEntity() ? oldBlockEntity.serializeNBT() : null;         if (oldBlockEntity != null) {             pContext.getLevel().removeBlockEntity(blockPos);         }         BlockState FrozenBlock = setFrozenBlock(oldState, oldBlockEntity, oldBlockEntityData);         pContext.getLevel().setBlockAndUpdate(blockPos, FrozenBlock);     }     public static BlockState setFrozenBlock(BlockState blockState, @Nullable BlockEntity blockEntity, @Nullable CompoundTag blockEntityData) {         BlockState FrozenBlock = BlockRegister.FROZEN_BLOCK.get().defaultBlockState();         ((FrozenBlock) FrozenBlock.getBlock()).setOldBlockFields(blockState, blockEntity, blockEntityData);         return FrozenBlock;     }  
    • It is an issue with quark - update it to this build: https://www.curseforge.com/minecraft/mc-mods/quark/files/3642325
  • Topics

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.