Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,56 @@
}
});

/**
* Called before an Entity is added to a ServerLevel.
*
* <p>If return value is {@code false} entity will not be added to a server.</p>
*
* <p>Should be used when you want to add another entity instead of added or to block adding specific entities.</p>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still doesnt explain when this is invoked and why its different from the other entity events.

Im a little bit concerned about this injection point as it doenst really do anything special: https://mcsrc.dev/1/26.1-snapshot-11/net/minecraft/server/level/ServerLevel#L951-953 and is possibly going to miss certain types of spawns.

*
* {@snippet :
* ServerEntityEvents.ALLOW_FRESH_LOAD.register((entity, level) -> {
* // Spawn with 25% chance zombie instead of creeper
* if (entity instanceof Creeper && level.getRandom().nextFloat() <= 0.25f) {
* var zombie = EntityType.ZOMBIE.create(level, null, entity.blockPosition(), EntitySpawnReason.EVENT, true, false);
* if (zombie != null) return !level.addFreshEntity(zombie);
* }
* return true;
* });
* }
*/
public static final Event<ServerEntityEvents.AllowFreshLoad> ALLOW_FRESH_LOAD = EventFactory.createArrayBacked(ServerEntityEvents.AllowFreshLoad.class, callbacks -> (entity, level) -> {
boolean bl = true;

for (AllowFreshLoad callback : callbacks) {
bl = bl && callback.onFreshLoad(entity, level);
}

return bl;
});

/**
* Called when an Entity is added to a ServerLevel.
*
* <p>When this event is called, the entity is already in the level.</p>
*
* <p>Should be used when you need to do something after entity summon, naturally spawn or any other add reason.</p>
* <p>If you need to do something after entity every entity load (not the first one) use ENTITY_LOAD event.</p>

Check failure on line 77 in fabric-lifecycle-events-v1/src/main/java/net/fabricmc/fabric/api/event/lifecycle/v1/ServerEntityEvents.java

View workflow job for this annotation

GitHub Actions / build

<p> tag should be preceded with an empty line.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do wonder if the regular ENTITY_LOAD event could do with some sort of context instead? E.g being able to target only spawn eggs, or natural spawns. It would be a lot more robust than just another event that is fired within a questionable method.

*
* {@snippet :
* ServerEntityEvents.AFTER_FRESH_LOAD.register((entity, level) -> {
* if (entity instanceof Creeper) {
* level.players().forEach(player -> player.sendSystemMessage(Component.literal("Creeper was added")));
* }
* });
* }
*/
public static final Event<ServerEntityEvents.AfterFreshLoad> AFTER_FRESH_LOAD = EventFactory.createArrayBacked(ServerEntityEvents.AfterFreshLoad.class, callbacks -> (entity, level) -> {
for (AfterFreshLoad callback : callbacks) {
callback.afterFreshLoad(entity, level);
}
});

/**
* Called when an Entity is unloaded from a ServerLevel.
*
Expand Down Expand Up @@ -68,6 +118,16 @@
void onLoad(Entity entity, ServerLevel level);
}

@FunctionalInterface
public interface AllowFreshLoad {
boolean onFreshLoad(Entity entity, ServerLevel level);
}

@FunctionalInterface
public interface AfterFreshLoad {
void afterFreshLoad(Entity entity, ServerLevel level);
}

@FunctionalInterface
public interface Unload {
void onUnload(Entity entity, ServerLevel level);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,18 @@

import java.util.function.BooleanSupplier;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.Entity;

import net.fabricmc.fabric.api.event.lifecycle.v1.ServerEntityEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;

@Mixin(ServerLevel.class)
Expand All @@ -40,4 +44,18 @@ private void startLevelTick(BooleanSupplier shouldKeepTicking, CallbackInfo ci)
private void endLevelTick(BooleanSupplier shouldKeepTicking, CallbackInfo ci) {
ServerTickEvents.END_LEVEL_TICK.invoker().onEndTick((ServerLevel) (Object) this);
}

@WrapOperation(method = "addFreshEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/level/ServerLevel;addEntity(Lnet/minecraft/world/entity/Entity;)Z"))
private boolean afterEntityFreshLoad(ServerLevel instance, Entity entity, Operation<Boolean> original) {
ServerLevel serverLevel = (ServerLevel) (Object) this;
if (!ServerEntityEvents.ALLOW_FRESH_LOAD.invoker().onFreshLoad(entity, serverLevel)) return false;

boolean result = original.call(instance, entity);

if (result) {
ServerEntityEvents.AFTER_FRESH_LOAD.invoker().afterFreshLoad(entity, serverLevel);
}

return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ public void onInitialize() {
}
});

ServerEntityEvents.ALLOW_FRESH_LOAD.register((entity, level) -> {
if (PRINT_SERVER_ENTITY_MESSAGES) {
logger.info("[SERVER] ALLOW FRESH LOAD {} IN {}", entity, level);
}

return true;
});

ServerEntityEvents.AFTER_FRESH_LOAD.register((entity, level) -> {
if (PRINT_SERVER_ENTITY_MESSAGES) {
logger.info("[SERVER] FRESH LOADED {} IN {}", entity, level);
}
});

ServerEntityEvents.ENTITY_UNLOAD.register((entity, level) -> {
this.serverEntities.remove(entity);

Expand Down
Loading