After having some discussions with @_CodeGreen, I believe that the current behavior of create_entity when fast replacing an entity is a bug. There are valid reasons to not use apply_upgrade() to upgrade an entity but use create_entity with fast replace and in that case, the game fires different events:
- when using apply_upgrade():
- script_raised_destroy for old entity
- script_raised_built for new entity
- on_object_destroyed for old entity
- when using create_entity with fast_replace = true
- script_raised_built for new entity
- on_object_destroyed for old entity
but no "script_raised_destroyed" for the old entity that was fast replaced.
hgschmie wrote: Wed May 06, 2026 4:12 am I have a mod (https://mods.factorio.com/mod/miniloader-redux) where each entity relies on creation/deletion events to maintain internal state. This works fine in the regular game.
I register on_built_entity, on_robot_built_entity, on_space_platform_built_entity, script_raised_built, script_raised_revive as creation events. Each does the same: create an instance of my mod entity around the entity in the event.
I register on_player_mined_entity, on_robot_mined_entity, on_space_platform_mined_entity, script_raised_destroy as deletion events. Same here: If that entity gets destroyed, take the mod entity and its internal pieces down.
In addition, I also register the entity with script.register_on_object_destroyed() and catch the on_object_destroyed event.
In the normal game, when a user uses the upgrade planner, I receive:
- on_robot_mined_entity event for the old entity
- on_robot_built_entity event for the new entity
- on_object_destroyed for the old entity
If a player uses fast replacement (pasting a new entity over the old one), I receive:
- on_player_mined_entity for the old entity
- on_built_entity for the new entity
- on_object_destroyed for the old entity
Now, there is a mod (Blueprint Sandboxes, https://mods.factorio.com/mod/blueprint-sandboxes), which seems to be pretty popular. It creates a magic surface where entities can be built (for interesting values of "built"; It seems that it actually places ghosts which then get revived). If an entity on this surface is either fast-replaced or the upgrade planner is used, it does this:
In that case, I receiveCode: Select all
-- here entity is valid and should be upgraded local targetEntity, targetQuality = entity.get_upgrade_target() local options = { name = targetEntity.name, position = entity.position, direction = entity.direction, quality = targetQuality, force = entity.force, fast_replace = true, spill = false, raise_built = true, } local result = entity.surface.create_entity(options)
- script_raised_built for the new entity
- on_object_destroyed for the old entity
Most importantly I do not receive any destroy event. As fast replacing (same as upgrading) in all other scenarios sends a mined_entity event before sending a built_entity event, I would have expected that in this scenario (raise_built = true and fast_replace = true), the engine would e.g. send out script_raised_destroy. Right now, the creation of the "upgraded" or "fast replaced" entity fails for me because the internal entities can not be created on top of each other.
So I guess, this is 50% asking for clarification, 50% bug report. I believe the engine *should* raise script_raised_destroy IFF create_entity is called with raise_built and fast_replace both being true *and* it is actually fast-replacing an entity (same position and surface and fast-replaceable). Otherwise, custom entities (such as mine) get confused because the "regular" 'destroy old, create new' event sequence does not happen.
