diff --git a/src/main/java/io/github/fabriccommunity/events/mixin/advancement/MixinInventoryChangedCriterion.java b/src/main/java/io/github/fabriccommunity/events/mixin/advancement/MixinInventoryChangedCriterion.java index e9613bb..e8d575c 100644 --- a/src/main/java/io/github/fabriccommunity/events/mixin/advancement/MixinInventoryChangedCriterion.java +++ b/src/main/java/io/github/fabriccommunity/events/mixin/advancement/MixinInventoryChangedCriterion.java @@ -11,6 +11,9 @@ import net.minecraft.item.ItemStack; import net.minecraft.server.network.ServerPlayerEntity; +/** + * @author B0undaryBreaker + */ @Mixin(InventoryChangedCriterion.class) public class MixinInventoryChangedCriterion { @Inject(method = "trigger(Lnet/minecraft/server/network/ServerPlayerEntity;Lnet/minecraft/entity/player/PlayerInventory;Lnet/minecraft/item/ItemStack;III)V", at = @At("HEAD")) diff --git a/src/main/java/io/github/fabriccommunity/events/mixin/advancement/MixinItemDurabilityChangedCriterion.java b/src/main/java/io/github/fabriccommunity/events/mixin/advancement/MixinItemDurabilityChangedCriterion.java index f8e4392..c990e48 100644 --- a/src/main/java/io/github/fabriccommunity/events/mixin/advancement/MixinItemDurabilityChangedCriterion.java +++ b/src/main/java/io/github/fabriccommunity/events/mixin/advancement/MixinItemDurabilityChangedCriterion.java @@ -10,6 +10,9 @@ import net.minecraft.item.ItemStack; import net.minecraft.server.network.ServerPlayerEntity; +/** + * @author B0undaryBreaker + */ @Mixin(ItemDurabilityChangedCriterion.class) public class MixinItemDurabilityChangedCriterion { @Inject(method = "trigger", at = @At("HEAD")) diff --git a/src/main/java/io/github/fabriccommunity/events/mixin/advancement/MixinOnKilledCriterion.java b/src/main/java/io/github/fabriccommunity/events/mixin/advancement/MixinOnKilledCriterion.java index d01fd76..5c8e473 100644 --- a/src/main/java/io/github/fabriccommunity/events/mixin/advancement/MixinOnKilledCriterion.java +++ b/src/main/java/io/github/fabriccommunity/events/mixin/advancement/MixinOnKilledCriterion.java @@ -12,6 +12,9 @@ import net.minecraft.entity.damage.DamageSource; import net.minecraft.server.network.ServerPlayerEntity; +/** + * @author B0undaryBreaker + */ @Mixin(OnKilledCriterion.class) public class MixinOnKilledCriterion { @Inject(method = "trigger", at = @At("HEAD")) diff --git a/src/main/java/io/github/fabriccommunity/events/mixin/advancement/MixinPlayerHurtEntityCriterion.java b/src/main/java/io/github/fabriccommunity/events/mixin/advancement/MixinPlayerHurtEntityCriterion.java index 417ac87..69d121d 100644 --- a/src/main/java/io/github/fabriccommunity/events/mixin/advancement/MixinPlayerHurtEntityCriterion.java +++ b/src/main/java/io/github/fabriccommunity/events/mixin/advancement/MixinPlayerHurtEntityCriterion.java @@ -11,6 +11,9 @@ import net.minecraft.entity.damage.DamageSource; import net.minecraft.server.network.ServerPlayerEntity; +/** + * @author b0undary + */ @Mixin(PlayerHurtEntityCriterion.class) public class MixinPlayerHurtEntityCriterion { @Inject(method = "trigger", at = @At("HEAD")) diff --git a/src/main/java/io/github/fabriccommunity/events/mixin/spawn/MixinSpawnHelper.java b/src/main/java/io/github/fabriccommunity/events/mixin/spawn/MixinSpawnHelper.java index 538dce5..3fa7c7b 100644 --- a/src/main/java/io/github/fabriccommunity/events/mixin/spawn/MixinSpawnHelper.java +++ b/src/main/java/io/github/fabriccommunity/events/mixin/spawn/MixinSpawnHelper.java @@ -1,14 +1,24 @@ package io.github.fabriccommunity.events.mixin.spawn; +import java.util.Random; + import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import io.github.fabriccommunity.events.impl.EntitySpawnImpl; +import io.github.fabriccommunity.events.world.WorldGenEvents; import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; import net.minecraft.world.ServerWorldAccess; import net.minecraft.world.SpawnHelper; +import net.minecraft.world.WorldView; +import net.minecraft.world.biome.Biome; /** * @author Valoeghese @@ -30,4 +40,14 @@ private static void entitySpawnEventNatural(ServerWorld self, Entity entity) { private static void entitySpawnEventChunk(ServerWorldAccess self, Entity entity) { EntitySpawnImpl.spawnEntityV(self, entity); } + + @Inject(at = @At("RETURN"), method = "populateEntities") + private static void onPopulateEntities(ServerWorldAccess world, Biome biome, int chunkX, int chunkZ, Random random, CallbackInfo info) { + WorldGenEvents.POPULATE_ENTITIES.invoker().onPopulateEntities(world, biome, chunkX, chunkZ, random, MixinSpawnHelper::getEntitySpawnPos); + } + + @Shadow + private static BlockPos getEntitySpawnPos(WorldView world, EntityType entityType, int x, int z) { + throw new RuntimeException("Mixin getEntitySpawnPos failed to apply!"); + } } diff --git a/src/main/java/io/github/fabriccommunity/events/mixin/world/MixinChunkGenerator.java b/src/main/java/io/github/fabriccommunity/events/mixin/world/MixinChunkGenerator.java new file mode 100644 index 0000000..5eb7ce1 --- /dev/null +++ b/src/main/java/io/github/fabriccommunity/events/mixin/world/MixinChunkGenerator.java @@ -0,0 +1,29 @@ +package io.github.fabriccommunity.events.mixin.world; + +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 io.github.fabriccommunity.events.world.WorldGenEvents; +import net.minecraft.world.ChunkRegion; +import net.minecraft.world.gen.StructureAccessor; +import net.minecraft.world.gen.chunk.ChunkGenerator; + +/** + * @author Valoeghese + */ +@Mixin(ChunkGenerator.class) +public class MixinChunkGenerator { + @Inject(at = @At("HEAD"), method = "generateFeatures", cancellable = true) + private void onStartGenerateFeatures(ChunkRegion region, StructureAccessor accessor, CallbackInfo info) { + if (WorldGenEvents.GENERATE_FEATURES_START.invoker().onStartGenerateFeatures((ChunkGenerator) (Object) this, region, accessor)) { + info.cancel(); + } + } + + @Inject(at = @At("RETURN"), method = "generateFeatures") + private void onEndGenerateFeatures(ChunkRegion region, StructureAccessor accessor, CallbackInfo info) { + WorldGenEvents.GENERATE_FEATURES_END.invoker().onEndGenerateFeatures((ChunkGenerator) (Object) this, region, accessor); + } +} diff --git a/src/main/java/io/github/fabriccommunity/events/play/PlayerAdvancementEvents.java b/src/main/java/io/github/fabriccommunity/events/play/PlayerAdvancementEvents.java index 918deea..776d4f2 100644 --- a/src/main/java/io/github/fabriccommunity/events/play/PlayerAdvancementEvents.java +++ b/src/main/java/io/github/fabriccommunity/events/play/PlayerAdvancementEvents.java @@ -39,10 +39,10 @@ public class PlayerAdvancementEvents { */ public static final Event ITEM_DURABILITY_CHANGED_WILDCARD = EventFactory.createArrayBacked(ItemDurabilityChanged.class, listeners -> (player, stack, damage) -> { - for (ItemDurabilityChanged listener : listeners) { - listener.onItemDurabilityChange(player, stack, damage); - } - }); + for (ItemDurabilityChanged listener : listeners) { + listener.onItemDurabilityChange(player, stack, damage); + } + }); /** * An event run when the durability of a stack of is changed. @@ -58,10 +58,10 @@ public static Event itemDurabilityChanged(Item item) { */ public static final Event PLAYER_HURT_ENTITY_WILDCARD = EventFactory.createArrayBacked(PlayerHurtEntity.class, listeners -> (attacker, victim, source, dealt, taken, blocked) -> { - for (PlayerHurtEntity listener : listeners) { - listener.onPlayerHurtEntity(attacker, victim, source, dealt, taken, blocked); - } - }); + for (PlayerHurtEntity listener : listeners) { + listener.onPlayerHurtEntity(attacker, victim, source, dealt, taken, blocked); + } + }); /** * An event run when a player hurts a specific type of entity. @@ -77,10 +77,10 @@ public static Event playerHurtEntity(EntityType type) { */ public static final Event PLAYER_KILLED_ENTITY_WILDCARD = EventFactory.createArrayBacked(PlayerKilledEntity.class, listeners -> (killer, victim, source) -> { - for (PlayerKilledEntity listener : listeners) { - listener.onPlayerKillEntity(killer, victim, source); - } - }); + for (PlayerKilledEntity listener : listeners) { + listener.onPlayerKillEntity(killer, victim, source); + } + }); /** * An event run when a play kills a specific type of entity. diff --git a/src/main/java/io/github/fabriccommunity/events/util/SpawnPosGetter.java b/src/main/java/io/github/fabriccommunity/events/util/SpawnPosGetter.java new file mode 100644 index 0000000..ce7a7a5 --- /dev/null +++ b/src/main/java/io/github/fabriccommunity/events/util/SpawnPosGetter.java @@ -0,0 +1,14 @@ +package io.github.fabriccommunity.events.util; + +import net.minecraft.entity.EntityType; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.WorldView; + +/** + * Used by {@link WorldGenEvents.POPULATE_ENTITIES} as an accessible way to get the entity spawn pos for given x/z coordinates. + * @author Valoeghese + */ +@FunctionalInterface +public interface SpawnPosGetter { + BlockPos getEntitySpawnPos(WorldView world, EntityType entityType, int x, int z); +} diff --git a/src/main/java/io/github/fabriccommunity/events/world/WorldGenEvents.java b/src/main/java/io/github/fabriccommunity/events/world/WorldGenEvents.java index 9bbc9ed..acacd28 100644 --- a/src/main/java/io/github/fabriccommunity/events/world/WorldGenEvents.java +++ b/src/main/java/io/github/fabriccommunity/events/world/WorldGenEvents.java @@ -1,13 +1,19 @@ package io.github.fabriccommunity.events.world; +import java.util.Random; import java.util.concurrent.atomic.AtomicReference; +import io.github.fabriccommunity.events.util.SpawnPosGetter; import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.EventFactory; import net.minecraft.util.ActionResult; import net.minecraft.util.registry.Registry; +import net.minecraft.world.ChunkRegion; +import net.minecraft.world.ServerWorldAccess; import net.minecraft.world.biome.Biome; import net.minecraft.world.biome.source.BiomeSource; +import net.minecraft.world.gen.StructureAccessor; +import net.minecraft.world.gen.chunk.ChunkGenerator; /** * A collection of events pertaining to world generation. @@ -28,8 +34,39 @@ public final class WorldGenEvents { return ActionResult.PASS; }); + /** + * Called on feature placement to allow for mods to modify the chunk without using mojang's "features", or even replace chunk feature population, among various other things. + */ + public static final Event GENERATE_FEATURES_START = EventFactory.createArrayBacked(GenerateFeaturesStart.class, listeners -> (generator, region, structures) -> { + for (GenerateFeaturesStart listener : listeners) { + if (listener.onStartGenerateFeatures(generator, region, structures)) { + return true; + } + } + + return false; + }); + /** * Called at the end of biome placement to allow for mods to edit the biome to be placed. + */ + public static final Event GENERATE_FEATURES_END = EventFactory.createArrayBacked(GenerateFeaturesEnd.class, listeners -> (generator, region, structures) -> { + for (GenerateFeaturesEnd listener : listeners) { + listener.onEndGenerateFeatures(generator, region, structures); + } + }); + + /** + * Called on chunk generation entity population to allow for mods to spawn entities with their own logic. + */ + public static final Event POPULATE_ENTITIES = EventFactory.createArrayBacked(PopulateEntities.class, listeners -> (world, biome, chunkX, chunkZ, random, spawnPosGetter) -> { + for (PopulateEntities listener : listeners) { + listener.onPopulateEntities(world, biome, chunkX, chunkZ, random, spawnPosGetter); + } + }); + + /** + * Called on feature placement to allow for mods to modify the chunk without using mojang's "features", among various other things. * @author Valoeghese */ @FunctionalInterface @@ -51,4 +88,33 @@ public interface BiomePlacement { */ ActionResult onBiomePlace(BiomeSource source, Registry registry, final Biome original, AtomicReference biome, int genX, int genZ); } + + /** + * Called on feature placement to allow for mods to modify the chunk without using mojang's "features", or even replace chunk feature population, among various other things. + * @return true to cancel further event processing and not place features through vanilla's method. false to pass on to further event processing. + * @author Valoeghese + */ + @FunctionalInterface + public interface GenerateFeaturesStart { + boolean onStartGenerateFeatures(ChunkGenerator generator, ChunkRegion region, StructureAccessor accessor); + } + + /** + * Called on feature placement to allow for mods to modify the chunk without using mojang's "features", among various other things. + * @author Valoeghese + */ + @FunctionalInterface + public interface GenerateFeaturesEnd { + void onEndGenerateFeatures(ChunkGenerator generator, ChunkRegion region, StructureAccessor accessor); + } + + /** + * Called on chunk generation entity population to allow for mods to spawn entities with their own logic. + * @param spawnPosGetter a function used by vanilla to get the spawn block pos for the given x/z coordinates. + * @author Valoeghese + */ + @FunctionalInterface + public interface PopulateEntities { + void onPopulateEntities(ServerWorldAccess world, Biome biome, int chunkX, int chunkZ, Random random, SpawnPosGetter spawnPosGetter); + } } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index e2997fe..6dae753 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -6,8 +6,10 @@ "name": "Too Many Events", "description": "More events for modders to use", "authors": [ - "Valoeghese", - "HalfOf2" + "b0undary", + "HalfOf2", + "LeoCTH", + "Valoeghese" ], "license": "MIT", diff --git a/src/main/resources/toomanyevents.mixins.json b/src/main/resources/toomanyevents.mixins.json index e83d533..80f7f98 100644 --- a/src/main/resources/toomanyevents.mixins.json +++ b/src/main/resources/toomanyevents.mixins.json @@ -19,7 +19,8 @@ "spawn.MixinEntityType", "spawn.MixinMobSpawnerLogic", "spawn.MixinSpawnHelper", - "spawn.MixinSummonCommand" + "spawn.MixinSummonCommand", + "world.MixinChunkGenerator" ], "client": [ "client.MixinBackgroundRenderer", diff --git a/src/test/java/io/github/fabriccommunity/events/test/ChunkGenTest.java b/src/test/java/io/github/fabriccommunity/events/test/ChunkGenTest.java new file mode 100644 index 0000000..7bf6c1e --- /dev/null +++ b/src/test/java/io/github/fabriccommunity/events/test/ChunkGenTest.java @@ -0,0 +1,66 @@ +package io.github.fabriccommunity.events.test; + +import io.github.fabriccommunity.events.world.WorldGenEvents; +import net.fabricmc.api.ModInitializer; +import net.minecraft.block.Blocks; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.SpawnReason; +import net.minecraft.entity.passive.VillagerEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.biome.Biome; + +public class ChunkGenTest implements ModInitializer { + public static final boolean ENABLED = true; + + @Override + public void onInitialize() { + if (ENABLED) { + WorldGenEvents.GENERATE_FEATURES_START.register((generator, region, structures) -> { + if (random(region.getCenterChunkX(), region.getCenterChunkZ(), (int) region.getSeed(), 0b111) == 0) { + return true; // cancel further event processing + } + + return false; + }); + + WorldGenEvents.GENERATE_FEATURES_END.register((generator, region, structures) -> { + int x = region.getCenterChunkX() * 16; + int z = region.getCenterChunkZ() * 16; + BlockPos.Mutable e = new BlockPos.Mutable(x, 0, z); + + for (int y = 128; y >= 32; --y) { + e.setY(y); + region.setBlockState(e, Blocks.AIR.getDefaultState(), 3); + } + }); + + WorldGenEvents.POPULATE_ENTITIES.register((world, biome, chunkX, chunkZ, random, spawnPosGetter) -> { + if (biome.getCategory() != Biome.Category.OCEAN) { + int target = random.nextInt(3); + + for (int i = 0; i < target; ++i) { + int ex = (chunkX << 4) + random.nextInt(16); + int ez = (chunkZ << 4) + random.nextInt(16); + + VillagerEntity villager = EntityType.VILLAGER.create(world.toServerWorld()); + BlockPos pos = spawnPosGetter.getEntitySpawnPos(world, villager.getType(), ex, ez); + villager.refreshPositionAndAngles(pos, random.nextFloat() * 360.0f, 0.0f); + villager.headYaw = villager.yaw; + villager.bodyYaw = villager.yaw; + villager.initialize(world, world.getLocalDifficulty(pos), SpawnReason.STRUCTURE, null, null); + world.spawnEntityAndPassengers(villager); + } + } + }); + } + } + + private static int random(int x, int y, int seed, int mask) { + seed = 375462423 * seed + 672456235; + seed += x; + seed = 375462423 * seed + 672456235; + seed += y; + seed = 375462423 * seed + 672456235; + return seed & mask; + } +} diff --git a/src/test/resources/fabric.mod.json b/src/test/resources/fabric.mod.json index 6746e07..3b8d14b 100644 --- a/src/test/resources/fabric.mod.json +++ b/src/test/resources/fabric.mod.json @@ -17,6 +17,7 @@ "main": [ "io.github.fabriccommunity.events.test.BiomePlacementTest", "io.github.fabriccommunity.events.test.BlockPlaceTest", + "io.github.fabriccommunity.events.test.ChunkGenTest", "io.github.fabriccommunity.events.test.FoodTest", "io.github.fabriccommunity.events.test.EntitySpawnTest", "io.github.fabriccommunity.events.test.PlayerInteractionTest",