changes to entity load handler keeping it backwards compatible
This commit is contained in:
1 parent
85d60b283f
commit
448fde1f36
4 files changed
+83
-13
No files matched your search
@@ -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) {}
|
||||||
|
|||||||
@@ -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());
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user