From f5b53d5d10a0fcaf3e4bae7552e6a0cc26e0ab4e Mon Sep 17 00:00:00 2001 From: Richard Freimer Date: Wed, 7 Oct 2020 16:04:03 -0400 Subject: [PATCH] Add hooks to allow modification of structures spawn lists (#7344) --- .../server/MinecraftServer.java.patch | 42 +++-- .../world/gen/NoiseChunkGenerator.java.patch | 18 +++ .../structure/FortressStructure.java.patch | 12 ++ .../OceanMonumentStructure.java.patch | 12 ++ .../PillagerOutpostStructure.java.patch | 12 ++ .../feature/structure/Structure.java.patch | 23 ++- .../structure/SwampHutStructure.java.patch | 18 +++ .../common/extensions/IForgeStructure.java | 73 +++++++++ .../common/world/StructureSpawnManager.java | 134 ++++++++++++++++ .../world/StructureSpawnListGatherEvent.java | 150 ++++++++++++++++++ .../StructureSpawnListGatherEventTest.java | 59 +++++++ src/test/resources/META-INF/mods.toml | 4 +- 12 files changed, 538 insertions(+), 19 deletions(-) create mode 100644 patches/minecraft/net/minecraft/world/gen/feature/structure/FortressStructure.java.patch create mode 100644 patches/minecraft/net/minecraft/world/gen/feature/structure/OceanMonumentStructure.java.patch create mode 100644 patches/minecraft/net/minecraft/world/gen/feature/structure/PillagerOutpostStructure.java.patch create mode 100644 patches/minecraft/net/minecraft/world/gen/feature/structure/SwampHutStructure.java.patch create mode 100644 src/main/java/net/minecraftforge/common/extensions/IForgeStructure.java create mode 100644 src/main/java/net/minecraftforge/common/world/StructureSpawnManager.java create mode 100644 src/main/java/net/minecraftforge/event/world/StructureSpawnListGatherEvent.java create mode 100644 src/test/java/net/minecraftforge/debug/world/StructureSpawnListGatherEventTest.java diff --git a/patches/minecraft/net/minecraft/server/MinecraftServer.java.patch b/patches/minecraft/net/minecraft/server/MinecraftServer.java.patch index 7f5615b5d..aaddddd48 100644 --- a/patches/minecraft/net/minecraft/server/MinecraftServer.java.patch +++ b/patches/minecraft/net/minecraft/server/MinecraftServer.java.patch @@ -25,7 +25,15 @@ BiomeProvider biomeprovider = chunkgenerator.func_202090_b(); Random random = new Random(p_240786_0_.func_72905_C()); BlockPos blockpos = biomeprovider.func_225531_a_(0, p_240786_0_.func_181545_F(), 0, 256, (p_244265_0_) -> { -@@ -563,6 +565,7 @@ +@@ -449,6 +451,7 @@ + } + + private void func_213186_a(IChunkStatusListener p_213186_1_) { ++ net.minecraftforge.common.world.StructureSpawnManager.gatherEntitySpawns(); + ServerWorld serverworld = this.func_241755_D_(); + field_147145_h.info("Preparing start region for dimension {}", (Object)serverworld.func_234923_W_().func_240901_a_()); + BlockPos blockpos = serverworld.func_241135_u_(); +@@ -563,6 +566,7 @@ for(ServerWorld serverworld1 : this.func_212370_w()) { if (serverworld1 != null) { try { @@ -33,7 +41,7 @@ serverworld1.close(); } catch (IOException ioexception1) { field_147145_h.error("Exception closing the level", (Throwable)ioexception1); -@@ -611,6 +614,7 @@ +@@ -611,6 +615,7 @@ protected void func_240802_v_() { try { if (this.func_71197_b()) { @@ -41,7 +49,7 @@ this.field_211151_aa = Util.func_211177_b(); this.field_147147_p.func_151315_a(new StringTextComponent(this.field_71286_C)); this.field_147147_p.func_151321_a(new ServerStatusResponse.Version(SharedConstants.func_215069_a().getName(), SharedConstants.func_215069_a().getProtocolVersion())); -@@ -640,7 +644,10 @@ +@@ -640,7 +645,10 @@ this.func_240795_b_(longtickdetector); this.field_71296_Q = true; } @@ -52,7 +60,7 @@ this.func_71228_a((CrashReport)null); } } catch (Throwable throwable1) { -@@ -659,6 +666,7 @@ +@@ -659,6 +667,7 @@ field_147145_h.error("We were unable to save this crash report to disk."); } @@ -60,7 +68,7 @@ this.func_71228_a(crashreport); } finally { try { -@@ -667,6 +675,7 @@ +@@ -667,6 +676,7 @@ } catch (Throwable throwable) { field_147145_h.error("Exception stopping the server", throwable); } finally { @@ -68,7 +76,7 @@ this.func_71240_o(); } -@@ -768,6 +777,7 @@ +@@ -768,6 +778,7 @@ protected void func_71217_p(BooleanSupplier p_71217_1_) { long i = Util.func_211178_c(); @@ -76,7 +84,7 @@ ++this.field_71315_w; this.func_71190_q(p_71217_1_); if (i - this.field_147142_T >= 5000000000L) { -@@ -782,6 +792,7 @@ +@@ -782,6 +793,7 @@ Collections.shuffle(Arrays.asList(agameprofile)); this.field_147147_p.func_151318_b().func_151330_a(agameprofile); @@ -84,7 +92,7 @@ } if (this.field_71315_w % 6000 == 0) { -@@ -809,6 +820,7 @@ +@@ -809,6 +821,7 @@ long i1 = Util.func_211178_c(); this.field_213215_ap.func_181747_a(i1 - i); this.field_71304_b.func_76319_b(); @@ -92,7 +100,7 @@ } protected void func_71190_q(BooleanSupplier p_71190_1_) { -@@ -816,7 +828,8 @@ +@@ -816,7 +829,8 @@ this.func_193030_aL().func_73660_a(); this.field_71304_b.func_219895_b("levels"); @@ -102,7 +110,7 @@ this.field_71304_b.func_194340_a(() -> { return serverworld + " " + serverworld.func_234923_W_().func_240901_a_(); }); -@@ -827,6 +840,7 @@ +@@ -827,6 +841,7 @@ } this.field_71304_b.func_76320_a("tick"); @@ -110,7 +118,7 @@ try { serverworld.func_72835_b(p_71190_1_); -@@ -835,9 +849,11 @@ +@@ -835,9 +850,11 @@ serverworld.func_72914_a(crashreport); throw new ReportedException(crashreport); } @@ -122,7 +130,7 @@ } this.field_71304_b.func_219895_b("connection"); -@@ -912,7 +928,7 @@ +@@ -912,7 +929,7 @@ } public String getServerModName() { @@ -131,7 +139,7 @@ } public CrashReport func_71230_b(CrashReport p_71230_1_) { -@@ -925,6 +941,7 @@ +@@ -925,6 +942,7 @@ p_71230_1_.func_85056_g().func_189529_a("Data Packs", () -> { StringBuilder stringbuilder = new StringBuilder(); @@ -139,7 +147,7 @@ for(ResourcePackInfo resourcepackinfo : this.field_195577_ad.func_198980_d()) { if (stringbuilder.length() > 0) { stringbuilder.append(", "); -@@ -1271,6 +1288,7 @@ +@@ -1271,6 +1289,7 @@ this.func_184103_al().func_193244_w(); this.field_200258_al.func_240946_a_(this.field_195576_ac.func_240960_a_()); this.field_240765_ak_.func_195410_a(this.field_195576_ac.func_240970_h_()); @@ -147,7 +155,7 @@ }, this); if (this.func_213162_bc()) { this.func_213161_c(completablefuture::isDone); -@@ -1280,10 +1298,13 @@ +@@ -1280,10 +1299,13 @@ } public static DatapackCodec func_240772_a_(ResourcePackList p_240772_0_, DatapackCodec p_240772_1_, boolean p_240772_2_) { @@ -163,7 +171,7 @@ } else { Set set = Sets.newLinkedHashSet(); -@@ -1433,6 +1454,31 @@ +@@ -1433,6 +1455,31 @@ public abstract boolean func_213199_b(GameProfile p_213199_1_); @@ -195,7 +203,7 @@ public void func_223711_a(Path p_223711_1_) throws IOException { Path path = p_223711_1_.resolve("levels"); -@@ -1561,6 +1607,10 @@ +@@ -1561,6 +1608,10 @@ return this.field_240768_i_; } diff --git a/patches/minecraft/net/minecraft/world/gen/NoiseChunkGenerator.java.patch b/patches/minecraft/net/minecraft/world/gen/NoiseChunkGenerator.java.patch index 0655cdfa5..34bae10a6 100644 --- a/patches/minecraft/net/minecraft/world/gen/NoiseChunkGenerator.java.patch +++ b/patches/minecraft/net/minecraft/world/gen/NoiseChunkGenerator.java.patch @@ -11,3 +11,21 @@ chunkprimer.func_201637_h(blockpos$mutable); } +@@ -582,6 +582,9 @@ + } + + public List func_230353_a_(Biome p_230353_1_, StructureManager p_230353_2_, EntityClassification p_230353_3_, BlockPos p_230353_4_) { ++ List spawns = net.minecraftforge.common.world.StructureSpawnManager.getStructureSpawns(p_230353_2_, p_230353_3_, p_230353_4_); ++ if (spawns != null) return spawns; ++ if (false) {//Forge: We handle these hardcoded cases above in StructureSpawnManager#getStructureSpawns, but allow for insideOnly to be changed and allow for creatures to be spawned in ones other than just the witch hut + if (p_230353_2_.func_235010_a_(p_230353_4_, true, Structure.field_236374_j_).func_75069_d()) { + if (p_230353_3_ == EntityClassification.MONSTER) { + return Structure.field_236374_j_.func_202279_e(); +@@ -605,6 +608,7 @@ + return Structure.field_236378_n_.func_202279_e(); + } + } ++ } + + return super.func_230353_a_(p_230353_1_, p_230353_2_, p_230353_3_, p_230353_4_); + } diff --git a/patches/minecraft/net/minecraft/world/gen/feature/structure/FortressStructure.java.patch b/patches/minecraft/net/minecraft/world/gen/feature/structure/FortressStructure.java.patch new file mode 100644 index 000000000..fbcf92131 --- /dev/null +++ b/patches/minecraft/net/minecraft/world/gen/feature/structure/FortressStructure.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/gen/feature/structure/FortressStructure.java ++++ b/net/minecraft/world/gen/feature/structure/FortressStructure.java +@@ -30,7 +30,8 @@ + return FortressStructure.Start::new; + } + +- public List func_202279_e() { ++ @Override ++ public List getDefaultSpawnList() { + return field_202381_d; + } + diff --git a/patches/minecraft/net/minecraft/world/gen/feature/structure/OceanMonumentStructure.java.patch b/patches/minecraft/net/minecraft/world/gen/feature/structure/OceanMonumentStructure.java.patch new file mode 100644 index 000000000..c1f69dd00 --- /dev/null +++ b/patches/minecraft/net/minecraft/world/gen/feature/structure/OceanMonumentStructure.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/gen/feature/structure/OceanMonumentStructure.java ++++ b/net/minecraft/world/gen/feature/structure/OceanMonumentStructure.java +@@ -49,7 +49,8 @@ + return OceanMonumentStructure.Start::new; + } + +- public List func_202279_e() { ++ @Override ++ public List getDefaultSpawnList() { + return field_175803_h; + } + diff --git a/patches/minecraft/net/minecraft/world/gen/feature/structure/PillagerOutpostStructure.java.patch b/patches/minecraft/net/minecraft/world/gen/feature/structure/PillagerOutpostStructure.java.patch new file mode 100644 index 000000000..1920949af --- /dev/null +++ b/patches/minecraft/net/minecraft/world/gen/feature/structure/PillagerOutpostStructure.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/world/gen/feature/structure/PillagerOutpostStructure.java ++++ b/net/minecraft/world/gen/feature/structure/PillagerOutpostStructure.java +@@ -19,7 +19,8 @@ + super(p_i231977_1_, 0, true, true); + } + +- public List func_202279_e() { ++ @Override ++ public List getDefaultSpawnList() { + return field_214558_a; + } + diff --git a/patches/minecraft/net/minecraft/world/gen/feature/structure/Structure.java.patch b/patches/minecraft/net/minecraft/world/gen/feature/structure/Structure.java.patch index fdbc40458..6f8a122cd 100644 --- a/patches/minecraft/net/minecraft/world/gen/feature/structure/Structure.java.patch +++ b/patches/minecraft/net/minecraft/world/gen/feature/structure/Structure.java.patch @@ -5,7 +5,28 @@ import org.apache.logging.log4j.Logger; -public abstract class Structure { -+public abstract class Structure extends net.minecraftforge.registries.ForgeRegistryEntry> { ++public abstract class Structure extends net.minecraftforge.registries.ForgeRegistryEntry> implements net.minecraftforge.common.extensions.IForgeStructure { public static final BiMap> field_236365_a_ = HashBiMap.create(); private static final Map, GenerationStage.Decoration> field_236385_u_ = Maps.newHashMap(); private static final Logger field_208204_b = LogManager.getLogger(); +@@ -237,13 +237,18 @@ + } + + public List func_202279_e() { +- return ImmutableList.of(); ++ return getSpawnList(net.minecraft.entity.EntityClassification.MONSTER); + } + + public List func_214469_f() { +- return ImmutableList.of(); ++ return getSpawnList(net.minecraft.entity.EntityClassification.CREATURE); + } + ++ @Override ++ public final List getSpawnList(net.minecraft.entity.EntityClassification classification) { ++ return net.minecraftforge.common.world.StructureSpawnManager.getSpawnList(getStructure(), classification); ++ } ++ + public interface IStartFactory { + StructureStart create(Structure p_create_1_, int p_create_2_, int p_create_3_, MutableBoundingBox p_create_4_, int p_create_5_, long p_create_6_); + } diff --git a/patches/minecraft/net/minecraft/world/gen/feature/structure/SwampHutStructure.java.patch b/patches/minecraft/net/minecraft/world/gen/feature/structure/SwampHutStructure.java.patch new file mode 100644 index 000000000..0dfbc0c82 --- /dev/null +++ b/patches/minecraft/net/minecraft/world/gen/feature/structure/SwampHutStructure.java.patch @@ -0,0 +1,18 @@ +--- a/net/minecraft/world/gen/feature/structure/SwampHutStructure.java ++++ b/net/minecraft/world/gen/feature/structure/SwampHutStructure.java +@@ -24,11 +24,13 @@ + return SwampHutStructure.Start::new; + } + +- public List func_202279_e() { ++ @Override ++ public List getDefaultSpawnList() { + return field_202384_d; + } + +- public List func_214469_f() { ++ @Override ++ public List getDefaultCreatureSpawnList() { + return field_214559_aS; + } + diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeStructure.java b/src/main/java/net/minecraftforge/common/extensions/IForgeStructure.java new file mode 100644 index 000000000..a3ffe0514 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeStructure.java @@ -0,0 +1,73 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2020. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.common.extensions; + +import java.util.Collections; +import java.util.List; +import net.minecraft.entity.EntityClassification; +import net.minecraft.world.biome.MobSpawnInfo; +import net.minecraft.world.gen.feature.structure.Structure; + +public interface IForgeStructure +{ + default Structure getStructure() + { + return (Structure) this; + } + + /** + * Gets the default list of {@link EntityClassification#MONSTER} spawns for this structure. + * + * @apiNote Implement this over {@link Structure#getSpawnList()} + */ + default List getDefaultSpawnList() + { + return Collections.emptyList(); + } + + /** + * Gets the default list of {@link EntityClassification#CREATURE} spawns for this structure. + * + * @apiNote Implement this over {@link Structure#getCreatureSpawnList()} + */ + default List getDefaultCreatureSpawnList() + { + return Collections.emptyList(); + } + + /** + * Gets the default for if entity spawns are restricted to being inside of the pieces making up the structure or if being in the bounds of the overall structure + * is good enough. + * @return {@code true} if the location to check spawns for has to be inside of the pieces making up the structure, {@code false} otherwise. + */ + default boolean getDefaultRestrictsSpawnsToInside() + { + //The Pillager Outpost and Ocean Monument check the full structure by default instead of limiting themselves to being within the structure's bounds + return getStructure() != Structure.field_236366_b_ && getStructure() != Structure.field_236376_l_; + } + + /** + * Helper method to get the list of entity spawns for this structure for the given classification. + * @param classification The classification of entities. + * @apiNote This method is marked as final in {@link Structure} so as to not be overridden by modders and breaking support for + * {@link net.minecraftforge.event.world.StructureSpawnListGatherEvent}. + */ + List getSpawnList(EntityClassification classification); +} diff --git a/src/main/java/net/minecraftforge/common/world/StructureSpawnManager.java b/src/main/java/net/minecraftforge/common/world/StructureSpawnManager.java new file mode 100644 index 000000000..6886662bc --- /dev/null +++ b/src/main/java/net/minecraftforge/common/world/StructureSpawnManager.java @@ -0,0 +1,134 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2020. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.common.world; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import javax.annotation.Nullable; +import net.minecraft.entity.EntityClassification; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.biome.MobSpawnInfo; +import net.minecraft.world.gen.feature.structure.Structure; +import net.minecraft.world.gen.feature.structure.StructureManager; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.world.StructureSpawnListGatherEvent; +import net.minecraftforge.registries.ForgeRegistries; + +/** + * Class to help manage entity spawns inside of structures + */ +public class StructureSpawnManager +{ + private static Map, StructureSpawnInfo> structuresWithSpawns = Collections.emptyMap(); + + /** + * Gathers potential entity spawns for all the different registered structures. + * @apiNote Internal + */ + public static void gatherEntitySpawns() + { + //We use a linked hash map to ensure that we check the structures in an order that if there are multiple structures a position satisfies + // then we have the same behavior as vanilla as vanilla checks, swamp huts, pillager outposts, ocean monuments, and nether fortresses in + // that order. + Map, StructureSpawnInfo> structuresWithSpawns = new LinkedHashMap<>(); + gatherEntitySpawns(structuresWithSpawns, Structure.field_236374_j_); + gatherEntitySpawns(structuresWithSpawns, Structure.field_236366_b_); + gatherEntitySpawns(structuresWithSpawns, Structure.field_236376_l_); + gatherEntitySpawns(structuresWithSpawns, Structure.field_236378_n_); + for (Structure structure : ForgeRegistries.STRUCTURE_FEATURES.getValues()) + { + if (structure != Structure.field_236374_j_ && structure != Structure.field_236366_b_ && structure != Structure.field_236376_l_ && + structure != Structure.field_236378_n_) + { + //If we didn't already gather the spawns already to ensure we do vanilla ones already + // gather the spawns for this structure + gatherEntitySpawns(structuresWithSpawns, structure); + } + } + StructureSpawnManager.structuresWithSpawns = structuresWithSpawns; + } + + private static void gatherEntitySpawns(Map, StructureSpawnInfo> structuresWithSpawns, Structure structure) + { + StructureSpawnListGatherEvent event = new StructureSpawnListGatherEvent(structure); + MinecraftForge.EVENT_BUS.post(event); + ImmutableMap.Builder> builder = ImmutableMap.builder(); + event.getEntitySpawns().forEach((classification, spawns) -> { + if (!spawns.isEmpty()) + builder.put(classification, ImmutableList.copyOf(spawns)); + }); + Map> entitySpawns = builder.build(); + if (!entitySpawns.isEmpty()) + structuresWithSpawns.put(structure, new StructureSpawnInfo(entitySpawns, event.isInsideOnly())); + } + + /** + * Looks up if a given position is within a structure and returns any entity spawns that structure has for the given classification, or null if + * none are found. + * @param structureManager Structure Manager, used to check if a position is within a structure. + * @param classification Entity classification + * @param pos Position to get entity spawns of + */ + @Nullable + public static List getStructureSpawns(StructureManager structureManager, EntityClassification classification, BlockPos pos) + { + for (Entry, StructureSpawnInfo> entry : structuresWithSpawns.entrySet()) + { + Structure structure = entry.getKey(); + StructureSpawnInfo spawnInfo = entry.getValue(); + //Note: We check if the structure has spawns for a type first before looking at the world as it should be a cheaper check + if (spawnInfo.spawns.containsKey(classification) && structureManager.func_235010_a_(pos, spawnInfo.insideOnly, structure).isValid()) + return spawnInfo.spawns.get(classification); + } + return null; + } + + /** + * Gets the entity spawn lists for entities of a given classification for a given structure. + * @param structure The Structure + * @param classification The classification to lookup + */ + public static List getSpawnList(Structure structure, EntityClassification classification) + { + if (structuresWithSpawns.containsKey(structure)) + return structuresWithSpawns.get(structure).spawns.getOrDefault(classification, Collections.emptyList()); + return Collections.emptyList(); + } + + /** + * Helper class to keep track of spawns and if the spawns should be restricted to inside the structure pieces. + */ + private static class StructureSpawnInfo + { + private final Map> spawns; + private final boolean insideOnly; + + private StructureSpawnInfo(Map> spawns, boolean insideOnly) + { + this.spawns = spawns; + this.insideOnly = insideOnly; + } + } +} diff --git a/src/main/java/net/minecraftforge/event/world/StructureSpawnListGatherEvent.java b/src/main/java/net/minecraftforge/event/world/StructureSpawnListGatherEvent.java new file mode 100644 index 000000000..59a6d7429 --- /dev/null +++ b/src/main/java/net/minecraftforge/event/world/StructureSpawnListGatherEvent.java @@ -0,0 +1,150 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2020. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.event.world; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import net.minecraft.entity.EntityClassification; +import net.minecraft.world.biome.MobSpawnInfo; +import net.minecraft.world.gen.feature.structure.Structure; +import net.minecraftforge.eventbus.api.Event; +import net.minecraftforge.eventbus.api.EventPriority; + +/** + * This event fires when a Structure is gathering what mobs/creatures can spawn in it. + * + * In order to maintain the most compatibility possible with other mods' modifications to a structure, + * the event should be assigned a {@link net.minecraftforge.eventbus.api.EventPriority} as follows: + * + * - Additions : {@link EventPriority#HIGH} + * - Removals : {@link EventPriority#NORMAL} + * - Any other modification : {@link EventPriority#LOW} + * + * Be aware that another mod could have done an operation beforehand, so an expected value out of a vanilla structure might not + * always be the same, depending on other mods. + */ +public class StructureSpawnListGatherEvent extends Event +{ + + private final Structure structure; + private final Map> entitySpawns = new HashMap<>(); + private final Map> entitySpawnsUnmodifiableLists = new HashMap<>(); + private final Map> entitySpawnsUnmodifiable = Collections.unmodifiableMap(entitySpawnsUnmodifiableLists); + private boolean insideOnly; + + public StructureSpawnListGatherEvent(Structure structure) + { + this.structure = structure; + this.insideOnly = this.structure.getDefaultRestrictsSpawnsToInside(); + addEntitySpawns(EntityClassification.MONSTER, this.structure.getDefaultSpawnList()); + addEntitySpawns(EntityClassification.CREATURE, this.structure.getDefaultCreatureSpawnList()); + } + + /** + * @return Structure to add or remove spawns to/from. + */ + public Structure getStructure() + { + return structure; + } + + /** + * Change if entity spawn location checks are done against the entire bounds of the structure or only the inside the pieces of the structure. + * @param insideOnly {@code true} to restrict the spawn checks to inside the pieces of the structure, {@code false} to allow spawns outside + */ + public void setInsideOnly(boolean insideOnly) + { + this.insideOnly = insideOnly; + } + + /** + * Checks if spawns for the structure are restricted to being inside the individual pieces of the structure. + */ + public boolean isInsideOnly() + { + return insideOnly; + } + + /** + * Gets an unmodifiable view of the the list representing the entity spawns for the given classification. + * @param classification Entity Classification + * @return The list of spawns for the given classification. + */ + public List getEntitySpawns(EntityClassification classification) + { + return this.entitySpawnsUnmodifiableLists.getOrDefault(classification, Collections.emptyList()); + } + + /** + * Gets the internal spawn list for a given entity classification, or adds one if needed. (This includes adding it to the unmodifiable view) + */ + private List getOrCreateEntitySpawns(EntityClassification classification) + { + return this.entitySpawns.computeIfAbsent(classification, c -> { + List spawners = new ArrayList<>(); + //If the classification isn't in entitySpawns yet, also add an unmodifiable view of the list to + // the unmodifiable list spawn map + this.entitySpawnsUnmodifiableLists.put(c, Collections.unmodifiableList(spawners)); + return spawners; + }); + } + + /** + * Adds a spawn to the list of spawns for the given classification. + * @param classification Entity Classification + * @param spawner Spawner + */ + public void addEntitySpawn(EntityClassification classification, MobSpawnInfo.Spawners spawner) + { + getOrCreateEntitySpawns(classification).add(spawner); + } + + /** + * Adds spawns to the list of spawns for the given classification. + * @param classification Entity Classification + * @param spawners Spawners to add + */ + public void addEntitySpawns(EntityClassification classification, List spawners) + { + getOrCreateEntitySpawns(classification).addAll(spawners); + } + + /** + * Removes a spawn from the list of spawns for the given classification. + * @param classification Entity Classification + * @param spawner Spawner + */ + public void removeEntitySpawn(EntityClassification classification, MobSpawnInfo.Spawners spawner) + { + if (this.entitySpawns.containsKey(classification)) + this.entitySpawns.get(classification).remove(spawner); + } + + /** + * Gets an unmodifiable view of the map of spawns based on entity classification that is used to fill in the various spawn lists for the structure. + */ + public Map> getEntitySpawns() + { + return entitySpawnsUnmodifiable; + } +} \ No newline at end of file diff --git a/src/test/java/net/minecraftforge/debug/world/StructureSpawnListGatherEventTest.java b/src/test/java/net/minecraftforge/debug/world/StructureSpawnListGatherEventTest.java new file mode 100644 index 000000000..abd13888b --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/world/StructureSpawnListGatherEventTest.java @@ -0,0 +1,59 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2020. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.debug.world; + +import net.minecraft.entity.EntityClassification; +import net.minecraft.entity.EntityType; +import net.minecraft.world.biome.MobSpawnInfo; +import net.minecraft.world.gen.feature.structure.Structure; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.world.StructureSpawnListGatherEvent; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.fml.common.Mod; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@Mod(StructureSpawnListGatherEventTest.MODID) +public class StructureSpawnListGatherEventTest +{ + + public static final String MODID = "structure_spawn_list_event_test"; + private static final Logger LOGGER = LogManager.getLogger(MODID); + + public StructureSpawnListGatherEventTest() + { + MinecraftForge.EVENT_BUS.addListener(EventPriority.HIGH, this::onStructureSpawnListGather); + } + + private void onStructureSpawnListGather(StructureSpawnListGatherEvent event) + { + if (event.getStructure() == Structure.field_236375_k_) + { + event.addEntitySpawn(EntityClassification.MONSTER, new MobSpawnInfo.Spawners(EntityType.WITHER_SKELETON, 100, 5, 15)); + LOGGER.info("Adding wither skeleton spawns to strong holds"); + } + else if (event.getStructure() == Structure.field_236373_i_) + { + event.setInsideOnly(false); + event.addEntitySpawn(EntityClassification.MONSTER, new MobSpawnInfo.Spawners(EntityType.GUARDIAN, 100, 5, 15)); + LOGGER.info("Adding guardians spawns to shipwrecks"); + } + } +} diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index b65b59997..0b1c0c7a7 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -87,4 +87,6 @@ license="LGPL v2.1" [[mods]] modId="custom_tag_types_test" [[mods]] - modId="biome_loading_event_test" \ No newline at end of file + modId="biome_loading_event_test" +[[mods]] + modId="structure_spawn_list_event_test" \ No newline at end of file