Ух! Всем привет! На время пришлось отложить сайт. Слегка выгорел, да еще и учёба схватила за хвост. Так что попробую наверстать, но так как с сессией ещё не закончено, то и обещать ничего пока не буду.
С последнего поста прошло жутких две недели! Это прям вау, время, ты куда? 😀 К счастью, я таки нашел пару свободных вечеров и разобрался (ну +-) с новой для меня темой, а именно генерация структур с использованием NBT моделей. И, конечно же, создание этих моделей при помощи структурного блока. Раньше подобные вещи делались немного иначе… Но! Сейчас это не так важно, ведь от нового подхода я просто в восторге! Скажу сразу, что при написании многое опущено. И откровенно, некоторые вещи для меня так и остались на уровне «так надо». Так что этот гайд больше ознакомительного плана, а дальше уже будем вместе ковырять исходный код майна для расширения кругозора!
В прошлом уроке для алтаря нужно было самому прописывать каждый блок, это достаточно просто, легко и понятно, но вы представьте себе каково таким образом создавать более сложные объекты! Так что за всякими деревьями и колодцами мы оставляем определение особенности (feature), а сами переходим к созданию полноценных структур (structure).
Кстати, после предыдущего урока я узнал несколько новых вещей по поводу структур и не только, так что будут некоторые серьёзные изменения мода в целом (да и старые уроки нужно теперь обновить, но уже после завершения этих, т.к. я так никогда до мобов не доберусь), так что готовьтесь 😀
В чем вся прелесть такого подхода?
Как я и сказал выше – не нужно прописывать все блоки самому. У нас будет генерироваться построенная и сохраненная в nbt файл структура, которую мы добавим в соответствующую папку ресурсов мода.
Так что сейчас самое интересное! Стройкаааа!!
Вы можете использовать уже готовые постройки из любых своим миров, а также создать плоский мир для новых (можно и не плоский, но на ровном строить удобнее 😊). В первую очередь нам нужно получить тот самый структурный блок. Сделать это можно очень легко: при помощи команды give.
/give YOUR_NICK minecraft:structure_block
Вот и он!
Ставим его и видим различные режимы работы (кнопка над кнопкой DONE — их переключатель). Этот блок может быть блоком данных, загрузки или сохранения структуры. Сейчас нам нужно будет структуру сохранить.
Это интерфейс для сохранения структур. Первый набор чисел – положение в пространстве. Y = 1 значит, что наша структура будет сразу же над блоком. Второй – размеры самой структуры. Для примера я создал рамку структуры 10х10х10, но во время постройки размеры пришлось немного изменить.
И вот он результат. Снаружи:
И внутри:
Конечно же там есть и пара тайных мест) Но раскрывать не буду. Ещё я добавил сундук, но лута там пока не будет. Закинуть туда что-либо совсем не сложно, но я хочу сделать отдельный урок про свои таблицы лута и именно там мы пропишем новую таблицу конкретно этому сундуку. А пока оставим как есть.
Теперь эту модель можно смело брать из папки мира, а именно из папки generated. Перемещаем её в папку structures, которая находится в папке MODID нашего мода в data ресурсах (та, что рецепты), Полный путь должен быть приблизительно таким: data/tutorial_mod_gs/structures/file.nbt.
JAVA часть
Отличные новости! В результате этого урока у вас в моде будет пара новых файлов и папок!
В первую очередь добавим сам файл структуры: KillerHouseStructure.java. Полный путь к файлу у меня такой: mod.astler.tutorial_mod_gs.world.gen.fecture.structure. Но, конечно же, вы можете хоть всё в одну папу сбросить). Просто я стараюсь использовать названия аналогично игре. Так будет проще в случае увеличения количества файлов мода. И вам советую.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
package mod.astler.tutorial_mod_gs.world.gen.fecture.structure; import com.mojang.datafixers.Dynamic; import mod.astler.tutorial_mod_gs.world.ModFeatures; import mod.astler.tutorial_mod_gs.world.gen.fecture.structure.config.KillerHouseConfig; import net.minecraft.util.Rotation; import net.minecraft.util.SharedSeedRandom; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MutableBoundingBox; import net.minecraft.world.biome.Biome; import net.minecraft.world.biome.BiomeManager; import net.minecraft.world.gen.ChunkGenerator; import net.minecraft.world.gen.Heightmap; import net.minecraft.world.gen.feature.NoFeatureConfig; import net.minecraft.world.gen.feature.structure.Structure; import net.minecraft.world.gen.feature.structure.StructureStart; import net.minecraft.world.gen.feature.template.TemplateManager; import java.util.Random; import java.util.function.Function; public class KillerHouseStructure extends Structure<NoFeatureConfig> { public KillerHouseStructure(Function<Dynamic<?>, ? extends NoFeatureConfig> configFactory) { super(configFactory); } public String getStructureName() { return "Killer_House"; } @Override public int getSize() { return 1; } public Structure.IStartFactory getStartFactory() { return KillerHouseStructure.Start::new; } @Override public boolean func_225558_a_(BiomeManager biomeManager, ChunkGenerator<?> chunkGenerator, Random random, int chunkX, int chunkZ, Biome biome) { if (chunkGenerator.hasStructure(biome, this)) { return random.nextInt(10) == 0; } return false; } public static class Start extends StructureStart { public Start(Structure<?> structure, int p_i225806_2_, int p_i225806_3_, MutableBoundingBox p_i225806_4_, int p_i225806_5_, long p_i225806_6_) { super(structure, p_i225806_2_, p_i225806_3_, p_i225806_4_, p_i225806_5_, p_i225806_6_); } public void init(ChunkGenerator<?> generator, TemplateManager templateManagerIn, int chunkX, int chunkZ, Biome biomeIn) { int posX = chunkX << 4; int posZ = chunkZ << 4; int height1 = generator.func_222532_b(posX + 3, posZ + 3, Heightmap.Type.OCEAN_FLOOR_WG); int height2 = generator.func_222532_b(posX + 13, posZ + 3, Heightmap.Type.OCEAN_FLOOR_WG); int height3 = generator.func_222532_b(posX + 3, posZ + 13, Heightmap.Type.OCEAN_FLOOR_WG); int height4 = generator.func_222532_b(posX + 13, posZ + 13, Heightmap.Type.OCEAN_FLOOR_WG); if (height1 == height2 && height1 == height3 && height1 == height4 && height1 >= generator.getSeaLevel()) { BlockPos pos = new BlockPos(posX + 3, 90, posZ + 3); Rotation rotation = Rotation.values()[this.rand.nextInt(Rotation.values().length)]; this.components.add(new KillerHousePieces.Piece(templateManagerIn, pos, rotation)); this.recalculateStructureSize(); } } } } |
Рассмотрим по порядку. Сперва идёт конструктор, но мы всего-лишь наследуем родителя ничего не меняя. Далее идут имя и размер структуры. getStartFactory сообщает с чего начинать строить структуру, func_225558_a_ отвечает за шанс генерации структуры в чанке. Статический класс Start как раз и определяет место для генерации, а также собирает цельную структуру из частей. По такому принципу стоят деревни, аванпосты, иглу и т.д. Т.е. структуры, в которых может быть сразу несколько отдельных частей. Как вы можете увидеть, там же я прописал ещё не созданную KillerHousePieces.Piece. Это единственная часть нашей простой структуры, а значит нам и нужен только один кусочек (piece).
Теперь там же создаём файл KillerHousePieces.Piece и пишем туда такое содержимое:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
package mod.astler.tutorial_mod_gs.world.gen.fecture.structure; import net.minecraft.nbt.CompoundNBT; import net.minecraft.util.Mirror; import net.minecraft.util.ResourceLocation; import net.minecraft.util.Rotation; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; import net.minecraft.util.math.MutableBoundingBox; import net.minecraft.world.IWorld; import net.minecraft.world.gen.ChunkGenerator; import net.minecraft.world.gen.Heightmap; import net.minecraft.world.gen.feature.structure.TemplateStructurePiece; import net.minecraft.world.gen.feature.template.*; import java.util.Random; import static mod.astler.tutorial_mod_gs.world.ModStructurePieceType.KILLER_HOUSE; public class KillerHousePieces { private static final ResourceLocation model = new ResourceLocation("tutorial_mod_gs: killer_house"); public static class Piece extends TemplateStructurePiece { private final ResourceLocation resourceLocation; private final Rotation rotation; public Piece(TemplateManager templateManager, BlockPos p_i49313_3_, Rotation p_i49313_4_) { super(KILLER_HOUSE, 0); this.resourceLocation = model; this.templatePosition = p_i49313_3_; this.rotation = p_i49313_4_; this.loadTemplate(templateManager); } public Piece(TemplateManager templateManager, CompoundNBT p_i50566_2_) { super(KILLER_HOUSE, p_i50566_2_); this.resourceLocation = new ResourceLocation(p_i50566_2_.getString("Template")); this.rotation = Rotation.valueOf(p_i50566_2_.getString("Rot")); this.loadTemplate(templateManager); } private void loadTemplate(TemplateManager templateManager) { Template template = templateManager.getTemplateDefaulted(this.resourceLocation); PlacementSettings placementsettings = (new PlacementSettings()).setIgnoreEntities(true).setRotation(this.rotation).setMirror(Mirror.NONE).addProcessor(BlockIgnoreStructureProcessor.STRUCTURE_BLOCK).addProcessor(JigsawReplacementStructureProcessor.INSTANCE); this.setup(template, this.templatePosition, placementsettings); } protected void readAdditional(CompoundNBT tagCompound) { super.readAdditional(tagCompound); tagCompound.putString("Template", this.resourceLocation.toString()); tagCompound.putString("Rot", this.rotation.name()); } public boolean func_225577_a_(IWorld world, ChunkGenerator<?> chunkGenerator, Random nRandom, MutableBoundingBox nMutableBoundingBox, ChunkPos nChunkPos) { int posY = world.getHeight(Heightmap.Type.WORLD_SURFACE_WG, this.templatePosition.getX(), this.templatePosition.getZ()) - 1; this.templatePosition = new BlockPos(this.templatePosition.getX(), posY, this.templatePosition.getZ()); return super.func_225577_a_(world, chunkGenerator, nRandom, nMutableBoundingBox, nChunkPos); } @Override protected void handleDataMarker(String function, BlockPos pos, IWorld worldIn, Random rand, MutableBoundingBox sbb) { } } } |
KillerHousePieces содержит все кусочки структуры. Как я и говорил выше – структура эта состоит из одного элемента, так что Piece один. В нём есть два конструктора, один мы вызываем при создании, а второй при загрузке. Родительскому классу передаём переменную, которая ссылается на эту же часть. Её мы проинициализируем сразу после разбора этого фрагмента.
loadTemplate — загружает шаблон (тот самый файл-модель) структуры, а func_225577_a_ генерирует структуру на указанной высоте .
handleDataMarker пока оставим пустым, но именно тут мы будем указывать лут для сундука при помощи функционального блока.
Регистрация структур
По сути дела, мы можем зарегистрировать структуру аналогично тому, как мы это делали с блоками, предметами и предыдущей структурой. Но изучая примеры на гите я нашел этот и такой вариант регистрации мне понравился куда больше. В первую очередь из-за того, что переменная и её инициализация находятся в одном месте. А в прошлом варианте мы их разделяли. Т.е. вот наглядно:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package mod.astler.tutorial_mod_gs.world; import mod.astler.tutorial_mod_gs.TutorialGSMod; import mod.astler.tutorial_mod_gs.world.gen.fecture.structure.KillerHouseStructure; import mod.astler.tutorial_mod_gs.world.gen.fecture.structure.config.KillerHouseConfig; import net.minecraft.world.gen.feature.Feature; import net.minecraft.world.gen.feature.NoFeatureConfig; import net.minecraftforge.fml.RegistryObject; import net.minecraftforge.registries.DeferredRegister; import net.minecraftforge.registries.ForgeRegistries; public class ModFeatures { public static final DeferredRegister<Feature<?>> REGISTER = new DeferredRegister<>(ForgeRegistries.FEATURES, TutorialGSMod.MODID); public static final RegistryObject<KillerHouseStructure> KILLER_HOUSE = REGISTER.register("killer_house", () -> new KillerHouseStructure(KillerHouseConfig::deserialize)); public static final RegistryObject<SunAltarFeature> SUN_ALTAR = REGISTER.register("sun_altar", () -> new SunAltarFeature(NoFeatureConfig::deserialize)); } |
Новая версия, тут активно используются Supplier, в прошлых уроках мы уже с ними встречались, так что не останавливаемся.
Фактически мы тут объявляем регистр для особенностей FEATURES для нашего мода (передаём MODID). А уже при помощи этого регистра производим регистрацию самих особенностей.
Тут нет дополнительных аннотаций для методов или классов, но сам регистр нужно инициализировать в конструкторе мода:
1 2 3 4 5 6 7 8 9 |
public TutorialGSMod() { //... IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus(); ModFeatures.REGISTER.register(bus); //... } |
И последнее. Регистрируем и сам кусок структуры. Для них регистрация происходит куда проще, но при этом так же нужно будет добавить вызов класса в конструктор TutorialGSMod. Так что вот код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package mod.astler.tutorial_mod_gs.world; import mod.astler.tutorial_mod_gs.TutorialGSMod; import mod.astler.tutorial_mod_gs.world.gen.fecture.structure.KillerHousePieces; import net.minecraft.util.ResourceLocation; import net.minecraft.util.registry.Registry; import net.minecraft.world.gen.feature.structure.IStructurePieceType; public class ModStructurePieceType { public static final IStructurePieceType KILLER_HOUSE = register(KillerHousePieces.Piece::new, TutorialGSMod.MODID + ":survival_camp"); public static void init() {} private static IStructurePieceType register(IStructurePieceType type, String key) { return Registry.register(Registry.STRUCTURE_PIECE, new ResourceLocation(key), type); } } |
А вот финальная версия нового в конструкторе:
1 2 3 4 5 6 7 8 9 |
public TutorialGSMod() { //... IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus(); ModFeatures.REGISTER.register(bus); ModStructurePieceType.init(); //... } |
Для генерации в желаемых (или всех биомах) нужно зарегистрировать структуру дважды. Один раз как структуру, а второй раз как особенность. Не знаю зачем разработчикам такая странная особенность, но так сделали они, и так вынуждены делать мы.
Добавляем структуру аналогично генерации руды или особенности:
1 2 3 4 5 6 7 |
@SubscribeEvent public static void onInitBiomesGen(FMLCommonSetupEvent event) { for (Biome biome : ForgeRegistries.BIOMES) { biome.func_226711_a_(ModStructures.KILLER_HOUSE.func_225566_b_(IFeatureConfig.NO_FEATURE_CONFIG)); biome.addFeature(GenerationStage.Decoration.SURFACE_STRUCTURES, ModStructures.KILLER_HOUSE.func_225566_b_(IFeatureConfig.NO_FEATURE_CONFIG).func_227228_a_(Placement.NOPE.func_227446_a_(IPlacementConfig.NO_PLACEMENT_CONFIG))); } } |
Вроде… можно запускать!
На этом у меня всё, спасибо за внимание. На выходных займусь переработкой старых гайдов и наконец залью некоторые на гитхаб.
Пингбэк: Создание модов для Minecraft 1.15 – GeekStand