Jump to content
Search In
  • More options...
Find results that contain...
Find results in...

[1.7.10] Keeping track of mob death/despawn


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 post
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 post
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 post
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 post
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 post
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 post
Share on other sites

This is all way too complicated.

Make a Set of WeakReferences (

Collections.newSetFromMap(new WeakHashMap<>())

). Then to find out the number of alive entities, loop through that Set (it will be cleared automatically of entities that are garbage collected) and check if they are still alive. Those that aren't, are despawned, ignore them (don't remove them!).

Link to post
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 post
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 post
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 post
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.

Guest
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.



  • Recently Browsing

    No registered users viewing this page.

  • Posts

    • i currently try to find out how the world gen works, at the moment im working on a structure but i have some problems with the generation. i use this  as an example, i created everything for the generation, but the structure is not generating this is my structure this is the structure registry this is my world load event and this my biome loading event thanks for replies
    • I'm currently trying to make a new cauldron-type block, and I'm having difficulty implementing blockstates. Here's the relevant part of the alchemical cauldron's code:   public class AlchemicalCauldronBlock extends Block { public static final IntegerProperty LEVEL = IntegerProperty.create("level", 1, 3); private static final VoxelShape INSIDE = box(2.0D, 4.0D, 2.0D, 14.0D, 16.0D, 14.0D); protected static final VoxelShape SHAPE = VoxelShapes.join(VoxelShapes.block(), VoxelShapes.or(box(0.0D, 0.0D, 4.0D, 16.0D, 3.0D, 12.0D), box(4.0D, 0.0D, 0.0D, 12.0D, 3.0D, 16.0D), box(2.0D, 0.0D, 2.0D, 14.0D, 3.0D, 14.0D), INSIDE), IBooleanFunction.ONLY_FIRST); public AlchemicalCauldronBlock() { super(Properties.of(Material.METAL).sound(SoundType.METAL).harvestLevel(3).strength(2F,2F)); // this.registerDefaultState(this.stateDefinition.any().setValue(LEVEL, 0)); // this.registerDefaultState(this.stateDefinition.any().setValue(LEVEL, Integer.valueOf(0))); } ... When I try to run this with either of the two attempts at registerDefaultState uncommented, I get this error: https://pastebin.com/vPf3UryC. For convenience, I believe this part is most relevant: How can I avoid this? My registry classes look like this, if it helps: public class registry { public static final DeferredRegister<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, AlchemyPlus.MODID); public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, AlchemyPlus.MODID); public static void register() { IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); BLOCKS.register(modEventBus); ITEMS.register(modEventBus); registerItems.register(); registerBlocks.register(); } } public class registerBlocks { public static final RegistryObject<Block> ALCHEMICAL_CAULDRON = register("alchemical_cauldron", AlchemicalCauldronBlock::new); static void register() {} private static <T extends Block> RegistryObject<T> registerNoItem(String name, Supplier<T> block) { return registry.BLOCKS.register(name, block); } private static <T extends Block> RegistryObject<T> register(String name, Supplier<T> block) { RegistryObject<T> ret = registerNoItem(name, block); registry.ITEMS.register(name, () -> new BlockItem(ret.get(), new Item.Properties().tab(ItemGroup.TAB_BREWING))); return ret; } } Full GitHub repository available here.   (Other minor problem(?):  I copied the methods getShape and getInteractionShape from net.minecraft.block.CauldronBlock and I'm getting these weird warnings. What's up with that? Do I need to worry about it?)  
    • Personally I use IForgeRegistryEntry#getRegistryName and then ResourceLocation#getNameSpace to get the id of the object.
    • you can get a block form a ResourceLocation, take a look at NBTUtil#readBlockState, do somthing like that
    • I am running forge version 1.16.5 36.1.31 on a server with a lot of mods and when a player refreshes the server list, it sends a long error in the server console. Additionally, the server list displays "Can't connect to server", despite refreshing and actually being able to connect to the server. here's the links to the logs: https://gist.github.com/WaffleTraits/83885b1539d5711fabff73a91924e665 https://gist.github.com/WaffleTraits/5ae78e6eaa330422bbc9f7226faf60ce Here's what's displayed in console when a player refreshes server listing: https://hastebin.com/ativexeyoy.properties server is running on ubuntu 20.04 and is using java 8 for this forge instance.
  • Topics

  • Who's Online (See full list)

×
×
  • Create New...

Important Information

By using this site, you agree to our Privacy Policy.