changes to entity load handler keeping it backwards compatible

This commit is contained in:
gnembon committed 2021-06-25 20:09:15 -03:00
1 parent 85d60b283f
commit 448fde1f36
4 files changed
+83 -13

No files matched your search

+16 -8
View File
@@ -816,7 +816,10 @@ In case you want to pass an event handler that is not defined in your module, pl
### `entity_load_handler(descriptor / descriptors, function)`, `entity_load_handler(descriptor / descriptors, call_name, ... args?)` ### `entity_load_handler(descriptor / descriptors, function)`, `entity_load_handler(descriptor / descriptors, call_name, ... args?)`
Attaches a callback to when any entity matching the following type / types is loaded in the game, allowing to grab a handle Attaches a callback to when any entity matching the following type / types is loaded in the game, allowing to grab a handle
to an entity right when it is loaded to the world without querying them every tick. Callback expects one parameter - the entity. to an entity right when it is loaded to the world without querying them every tick. Callback expects two parameters - the entity,
and a boolean indicating if the entity was newly created(`true`) or just loaded from disk. Single argument functions accepting
only entities are allowed, but deprecated and will be removed at some point.
If callback is `null`, then the current entity handler, if present, is removed. Consecutive calls to `entity_load_handler` will add / subtract If callback is `null`, then the current entity handler, if present, is removed. Consecutive calls to `entity_load_handler` will add / subtract
of the currently targeted entity types pool. of the currently targeted entity types pool.
@@ -826,10 +829,10 @@ that it is not clear which player to use run the load call.
``` ```
// veryfast method of getting rid of all the zombies. Callback is so early, its packets haven't reached yet the clients // veryfast method of getting rid of all the zombies. Callback is so early, its packets haven't reached yet the clients
// so to save on log errors, removal of mobs needs to be scheduled for later. // so to save on log errors, removal of mobs needs to be scheduled for later.
entity_load_handler('zombie', _(e) -> schedule(0, _(outer(e)) -> modify(e, 'remove'))) entity_load_handler('zombie', _(e, new) -> schedule(0, _(outer(e)) -> modify(e, 'remove')))
// making all zombies immediately faster and less susceptible to friction of any sort // making all zombies immediately faster and less susceptible to friction of any sort
entity_load_handler('zombie', _(e) -> entity_event(e, 'on_tick', _(e) -> modify(e, 'motion', 1.2*e~'motion'))) entity_load_handler('zombie', _(e, new) -> entity_event(e, 'on_tick', _(e) -> modify(e, 'motion', 1.2*e~'motion')))
``` ```
Word of caution: entities can be loaded with chunks in various states, for instance when a chunk is being generated, this means Word of caution: entities can be loaded with chunks in various states, for instance when a chunk is being generated, this means
@@ -837,25 +840,30 @@ that accessing world blocks would cause the game to freeze due to force generati
sure to never assume the chunk is ready and use `entity_load_handler` to schedule actions around the loaded entity, sure to never assume the chunk is ready and use `entity_load_handler` to schedule actions around the loaded entity,
or manipulate entity directly. or manipulate entity directly.
Also, it is possible that mobs that spawn with world generation, while being 'added' have their metadata serialized and cached
internally (vanilla limitation), so some modifications to these entities may have no effect on them. This affects mobs created with
world generation.
For instance the following handler is safe, as it only accesses the entity directly. It makes all spawned pigmen jump For instance the following handler is safe, as it only accesses the entity directly. It makes all spawned pigmen jump
``` ```
/script run entity_load_handler('zombified_piglin', _(e) -> modify(e, 'motion', 0, 1, 0) ) /script run entity_load_handler('zombified_piglin', _(e, new) -> if(new, modify(e, 'motion', 0, 1, 0)) )
``` ```
But the following handler, attempting to despawn pigmen that spawn in portals, will cause the game to freeze due to cascading access to blocks that would cause neighbouring chunks But the following handler, attempting to despawn pigmen that spawn in portals, will cause the game to freeze due to cascading access to blocks that would cause neighbouring chunks
to force generate, causing also error messages for all pigmen caused by packets send after entity is removed by script. to force generate, causing also error messages for all pigmen caused by packets send after entity is removed by script.
``` ```
/script run entity_load_handler('zombified_piglin', _(e) -> if(block(pos(e))=='nether_portal', modify(e, 'remove') ) ) /script run entity_load_handler('zombified_piglin', _(e, new) -> if(new && block(pos(e))=='nether_portal', modify(e, 'remove') ) )
``` ```
Easiest method to circumvent these issues is delay the check, which may or may not cause cascade load to happen, but Easiest method to circumvent these issues is delay the check, which may or may not cause cascade load to happen, but
will definitely break the infinite chain. will definitely break the infinite chain.
``` ```
/script run entity_load_handler('zombified_piglin', _(e) -> schedule(0, _(outer(e)) -> if(block(pos(e))=='nether_portal', modify(e, 'remove') ) ) ) /script run entity_load_handler('zombified_piglin', _(e, new) -> if(new, schedule(0, _(outer(e)) -> if(block(pos(e))=='nether_portal', modify(e, 'remove') ) ) ) )
``` ```
But the best is to perform the check first time the entity will be ticked - giving the game all the time to ensure chunk But the best is to perform the check first time the entity will be ticked - giving the game all the time to ensure chunk
is fully loaded and entity processing, removing the tick handler is fully loaded and entity processing, removing the tick handler:
``` ```
/script run entity_load_handler('zombified_piglin', _(e) -> entity_event(e, 'on_tick', _(e) -> ( if(block(pos(e))=='nether_portal', modify(e, 'remove')); entity_event(e, 'on_tick', null) ) ) ) /script run entity_load_handler('zombified_piglin', _(e, new) -> if(new, entity_event(e, 'on_tick', _(e) -> ( if(block(pos(e))=='nether_portal', modify(e, 'remove')); entity_event(e, 'on_tick', null) ) ) ) )
``` ```
Looks little convoluted, but that's the safest method to ensure your app won't crash.
### `entity_event(e, event, function)`, `entity_event(e, event, call_name, ... args?)` ### `entity_event(e, event, function)`, `entity_event(e, event, call_name, ... args?)`
@@ -23,6 +23,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture; import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import static carpet.script.CarpetEventServer.Event.ENTITY_HANDLER;
import static carpet.script.CarpetEventServer.Event.ENTITY_LOAD; import static carpet.script.CarpetEventServer.Event.ENTITY_LOAD;
import static carpet.script.CarpetEventServer.Event.EXPLOSION; import static carpet.script.CarpetEventServer.Event.EXPLOSION;
import static carpet.script.CarpetEventServer.Event.LIGHTNING; import static carpet.script.CarpetEventServer.Event.LIGHTNING;
@@ -43,24 +44,50 @@ public class ServerWorld_scarpetMixin implements ServerWorldInterface
if (LIGHTNING.isNeeded()) LIGHTNING.onWorldEventFlag((ServerWorld) (Object)this, blockPos, bl2?1:0); if (LIGHTNING.isNeeded()) LIGHTNING.onWorldEventFlag((ServerWorld) (Object)this, blockPos, bl2?1:0);
} }
private ThreadLocal<Boolean> entityJustLoaded = ThreadLocal.withInitial(() -> false);
@Inject(method = "loadEntityUnchecked", at = @At( @Inject(method = "loadEntityUnchecked", at = @At(
value = "INVOKE", value = "INVOKE",
target = "Lnet/minecraft/server/world/ServerChunkManager;loadEntity(Lnet/minecraft/entity/Entity;)V" target = "Lnet/minecraft/server/world/ServerChunkManager;loadEntity(Lnet/minecraft/entity/Entity;)V"
)) ))
private void onEntityAddedToWorld(Entity entity, CallbackInfo ci) private void onEntityAddedToWorld(Entity entity, CallbackInfo ci)
{ {
CarpetEventServer.Event event = ENTITY_LOAD.get(entity.getType()); boolean justLoaded = entityJustLoaded.get();
entityJustLoaded.set(false);
CarpetEventServer.Event event = ENTITY_HANDLER.get(entity.getType());
if (event != null) if (event != null)
{ {
if (event.isNeeded()) if (event.isNeeded())
{ {
event.onEntityAction(entity); event.onEntityAction(entity, !justLoaded);
} }
} }
else else
{ {
CarpetSettings.LOG.error("Failed to handle entity "+entity.getType().getTranslationKey()); CarpetSettings.LOG.error("Failed to handle entity "+entity.getType().getTranslationKey());
} }
// deprecated usage
event = ENTITY_LOAD.get(entity.getType());
if (event != null)
{
if (event.isNeeded())
{
event.onEntityAction(entity, true);
}
}
else
{
CarpetSettings.LOG.error("Failed to handle entity "+entity.getType().getTranslationKey());
}
}
// 1.16only
@Inject(method = "loadEntity", at= @At("HEAD"))
private void onSideLoaded(Entity entity, CallbackInfoReturnable<Boolean> cir)
{
entityJustLoaded.set(true);
} }
@Inject(method = "createExplosion", at = @At("HEAD")) @Inject(method = "createExplosion", at = @At("HEAD"))
@@ -926,18 +926,20 @@ public class CarpetEventServer
} }
}; };
@Deprecated
public static String getEntityLoadEventName(EntityType<? extends Entity> et) public static String getEntityLoadEventName(EntityType<? extends Entity> et)
{ {
return "entity_loaded_" + ValueConversions.of(Registry.ENTITY_TYPE.getId(et)).getString(); return "entity_loaded_" + ValueConversions.of(Registry.ENTITY_TYPE.getId(et)).getString();
} }
@Deprecated
public static final Map<EntityType<? extends Entity>, Event> ENTITY_LOAD= new HashMap<EntityType<? extends Entity>, Event>() {{ public static final Map<EntityType<? extends Entity>, Event> ENTITY_LOAD= new HashMap<EntityType<? extends Entity>, Event>() {{
EntityType.get("zombie"); EntityType.get("zombie");
Registry.ENTITY_TYPE.forEach(et -> { Registry.ENTITY_TYPE.forEach(et -> {
put(et, new Event(getEntityLoadEventName(et), 1, true, false) put(et, new Event(getEntityLoadEventName(et), 1, true, false)
{ {
@Override @Override
public void onEntityAction(Entity entity) public void onEntityAction(Entity entity, boolean created)
{ {
handler.call( handler.call(
() -> Collections.singletonList(new EntityValue(entity)), () -> Collections.singletonList(new EntityValue(entity)),
@@ -948,6 +950,28 @@ public class CarpetEventServer
}); });
}}; }};
public static String getEntityHandlerEventName(EntityType<? extends Entity> et)
{
return "entity_handler_" + ValueConversions.of(Registry.ENTITY_TYPE.getId(et)).getString();
}
public static final Map<EntityType<? extends Entity>, Event> ENTITY_HANDLER= new HashMap<EntityType<? extends Entity>, Event>() {{
EntityType.get("zombie");
Registry.ENTITY_TYPE.forEach(et -> {
put(et, new Event(getEntityHandlerEventName(et), 2, true, false)
{
@Override
public void onEntityAction(Entity entity, boolean created)
{
handler.call(
() -> Arrays.asList(new EntityValue(entity), BooleanValue.of(created)),
() -> CarpetServer.minecraft_server.getCommandSource().withWorld((ServerWorld) entity.world).withLevel(CarpetSettings.runPermissionLevel)
);
}
});
});
}};
// on projectile thrown (arrow from bows, crossbows, tridents, snoballs, e-pearls // on projectile thrown (arrow from bows, crossbows, tridents, snoballs, e-pearls
public final String name; public final String name;
@@ -1038,7 +1062,7 @@ public class CarpetEventServer
public void onBlockPlaced(ServerPlayerEntity player, BlockPos pos, Hand enumhand, ItemStack itemstack) { } public void onBlockPlaced(ServerPlayerEntity player, BlockPos pos, Hand enumhand, ItemStack itemstack) { }
public void onEntityHandAction(ServerPlayerEntity player, Entity entity, Hand enumhand) { } public void onEntityHandAction(ServerPlayerEntity player, Entity entity, Hand enumhand) { }
public void onHandAction(ServerPlayerEntity player, Hand enumhand) { } public void onHandAction(ServerPlayerEntity player, Hand enumhand) { }
public void onEntityAction(Entity entity) { } public void onEntityAction(Entity entity, boolean created) { }
public void onDimensionChange(ServerPlayerEntity player, Vec3d from, Vec3d to, RegistryKey<World> fromDim, RegistryKey<World> dimTo) {} public void onDimensionChange(ServerPlayerEntity player, Vec3d from, Vec3d to, RegistryKey<World> fromDim, RegistryKey<World> dimTo) {}
public void onDamage(Entity target, float amount, DamageSource source) { } public void onDamage(Entity target, float amount, DamageSource source) { }
public void onRecipeSelected(ServerPlayerEntity player, Identifier recipe, boolean fullStack) {} public void onRecipeSelected(ServerPlayerEntity player, Identifier recipe, boolean fullStack) {}
+12 -1
View File
@@ -275,10 +275,21 @@ public class Entities {
if (funArg.function == null) if (funArg.function == null)
{ {
types.forEach(et -> events.removeBuiltInEvent(CarpetEventServer.Event.getEntityLoadEventName(et), (CarpetScriptHost) c.host)); types.forEach(et -> events.removeBuiltInEvent(CarpetEventServer.Event.getEntityLoadEventName(et), (CarpetScriptHost) c.host));
types.forEach(et -> events.removeBuiltInEvent(CarpetEventServer.Event.getEntityHandlerEventName(et), (CarpetScriptHost) c.host));
} }
else else
{ {
types.forEach(et -> events.addBuiltInEvent(CarpetEventServer.Event.getEntityLoadEventName(et), c.host, funArg.function, funArg.args)); ///compat
int argno = funArg.function.getArguments().size() - funArg.args.size();
if (argno == 1)
{
c.host.issueDeprecation("entity_load_handler() with single argument callback");
types.forEach(et -> events.addBuiltInEvent(CarpetEventServer.Event.getEntityLoadEventName(et), c.host, funArg.function, funArg.args));
}
else
{
types.forEach(et -> events.addBuiltInEvent(CarpetEventServer.Event.getEntityHandlerEventName(et), c.host, funArg.function, funArg.args));
}
} }
return new NumericValue(types.size()); return new NumericValue(types.size());
}); });