Merge branch 'master' into dev/7.0-2

This commit is contained in:
Zoë Gidiere
2025-02-28 11:36:23 -07:00
506 changed files with 3439 additions and 1420 deletions

View File

@@ -2,7 +2,7 @@
## Resource files
Current mapping version: je 1.21 to be 1.21.30
Current mapping version: je 1.21.4 to be 1.21.50
- `mapping/biomes.json` obtain from GeyserMC/mappings.
- `mapping/items.json` obtain from GeyserMC/mappings.

View File

@@ -46,7 +46,7 @@ public class AllayPlatform extends AbstractPlatform {
var dimension = wrapper.getAllayWorldGenerator().getDimension();
TerraAllayPlugin.INSTANCE.getPluginLogger().info(
"Replaced pack in chunk generator for world {}",
dimension.getWorld().getWorldData().getName() + ":" + dimension.getDimensionInfo().dimensionId()
dimension.getWorld().getWorldData().getDisplayName() + ":" + dimension.getDimensionInfo().dimensionId()
);
});
});

View File

@@ -73,7 +73,7 @@ public final class Mapping {
public static Map<String, String> getJeBlockDefaultProperties(String jeBlockIdentifier) {
Map<String, String> defaultProperties = JE_BLOCK_DEFAULT_PROPERTIES.get(jeBlockIdentifier);
if( defaultProperties == null) {
if(defaultProperties == null) {
TerraAllayPlugin.INSTANCE.getPluginLogger().warn("Failed to find default properties for {}", jeBlockIdentifier);
return Map.of();
}
@@ -85,8 +85,8 @@ public final class Mapping {
}
private static boolean initBiomeMapping() {
try (InputStream stream = Mapping.class.getClassLoader().getResourceAsStream("mapping/biomes.json")) {
if (stream == null) {
try(InputStream stream = Mapping.class.getClassLoader().getResourceAsStream("mapping/biomes.json")) {
if(stream == null) {
TerraAllayPlugin.INSTANCE.getPluginLogger().error("biomes mapping not found");
return false;
}
@@ -100,12 +100,12 @@ public final class Mapping {
}
private static boolean initItemMapping() {
try (InputStream stream = Mapping.class.getClassLoader().getResourceAsStream("mapping/items.json")) {
if (stream == null) {
try(InputStream stream = Mapping.class.getClassLoader().getResourceAsStream("mapping/items.json")) {
if(stream == null) {
TerraAllayPlugin.INSTANCE.getPluginLogger().error("items mapping not found");
return false;
}
Set<Entry<String, Map<String, Object>>> mappings = JSONUtils.from(stream, new TypeToken<Map<String, Map<String, Object>>>(){}).entrySet();
Set<Entry<String, Map<String, Object>>> mappings = JSONUtils.from(stream, new TypeToken<Map<String, Map<String, Object>>>() {}).entrySet();
mappings.forEach(mapping -> {
ItemType<?> item = ItemTypeSafeGetter
.name((String) mapping.getValue().get("bedrock_identifier"))
@@ -121,13 +121,13 @@ public final class Mapping {
}
private static boolean initBlockStateMapping() {
try (InputStream stream = Mapping.class.getClassLoader().getResourceAsStream("mapping/blocks.json")) {
if (stream == null) {
try(InputStream stream = Mapping.class.getClassLoader().getResourceAsStream("mapping/blocks.json")) {
if(stream == null) {
TerraAllayPlugin.INSTANCE.getPluginLogger().error("blocks mapping not found");
return false;
}
// noinspection unchecked
List<Map<String, Map<String, Object>>> mappings = (List<Map<String, Map<String, Object>>>) JSONUtils.from(stream, new TypeToken<Map<String, Object>>(){}).get("mappings");
List<Map<String, Map<String, Object>>> mappings = (List<Map<String, Map<String, Object>>>) JSONUtils.from(stream, new TypeToken<Map<String, Object>>() {}).get("mappings");
mappings.forEach(mapping -> {
JeBlockState jeState = createJeBlockState(mapping.get("java_state"));
BlockState beState = createBeBlockState(mapping.get("bedrock_state"));
@@ -141,12 +141,12 @@ public final class Mapping {
}
private static boolean initJeBlockDefaultProperties() {
try (InputStream stream = Mapping.class.getClassLoader().getResourceAsStream("je_block_default_states.json")) {
if (stream == null) {
try(InputStream stream = Mapping.class.getClassLoader().getResourceAsStream("je_block_default_states.json")) {
if(stream == null) {
TerraAllayPlugin.INSTANCE.getPluginLogger().error("je_block_default_states.json not found");
return false;
}
Map<String, Map<String, String>> states = JSONUtils.from(stream, new TypeToken<Map<String, Map<String, String>>>(){});
Map<String, Map<String, String>> states = JSONUtils.from(stream, new TypeToken<>() {});
for(Entry<String, Map<String, String>> entry : states.entrySet()) {
String identifier = entry.getKey();
Map<String, String> properties = entry.getValue();
@@ -161,7 +161,7 @@ public final class Mapping {
private static BlockState createBeBlockState(Map<String, Object> data) {
Getter getter = BlockStateSafeGetter
.name("minecraft:" + data.get("bedrock_identifier"));
if (data.containsKey("state")) {
if(data.containsKey("state")) {
// noinspection unchecked
convertValueType((Map<String, Object>) data.get("state")).forEach(getter::property);
}
@@ -170,8 +170,8 @@ public final class Mapping {
private static Map<String, Object> convertValueType(Map<String, Object> data) {
TreeMap<String, Object> result = new TreeMap<>();
for (Entry<String, Object> entry : data.entrySet()) {
if (entry.getValue() instanceof Number number) {
for(Entry<String, Object> entry : data.entrySet()) {
if(entry.getValue() instanceof Number number) {
// Convert double to int because the number in json is double
result.put(entry.getKey(), number.intValue());
} else {

View File

@@ -40,9 +40,8 @@ public class TerraAllayPlugin extends Plugin {
AllayGeneratorWrapper wrapper = new AllayGeneratorWrapper(preset);
AllayPlatform.GENERATOR_WRAPPERS.add(wrapper);
return wrapper.getAllayWorldGenerator();
} catch (IllegalArgumentException e) {
TerraAllayPlugin.INSTANCE.getPluginLogger().error("Fail to create world generator with preset: {}", preset);
TerraAllayPlugin.INSTANCE.getPluginLogger().error("Reason: {}", e.getMessage());
} catch(IllegalArgumentException e) {
TerraAllayPlugin.INSTANCE.getPluginLogger().error("Fail to create world generator with preset: {}", preset, e);
return Registries.WORLD_GENERATOR_FACTORIES.get("FLAT").apply("");
}
});

View File

@@ -17,7 +17,7 @@ public record AllayBlockType(BlockType<?> allayBlockType) implements com.dfsek.t
@Override
public boolean isSolid() {
return allayBlockType.getMaterial().isSolid();
return allayBlockType.getDefaultState().getBlockStateData().isSolid();
}
@Override

View File

@@ -21,8 +21,7 @@ public record AllayChunk(ServerWorld world, Chunk allayChunk) implements com.dfs
public void setBlock(int x, int y, int z, BlockState data, boolean physics) {
AllayBlockState allayBlockState = (AllayBlockState) data;
allayChunk.setBlockState(x, y, z, allayBlockState.allayBlockState());
boolean containsWater = allayBlockState.containsWater() || allayChunk.getBlockState(x, y, z).getBlockType().hasBlockTag(BlockTags.WATER);
if (containsWater) {
if (allayBlockState.containsWater() || allayChunk.getBlockState(x, y, z).getBlockType().hasBlockTag(BlockTags.WATER)) {
allayChunk.setBlockState(x, y, z, WATER, 1);
}
}

View File

@@ -26,8 +26,7 @@ public record AllayProtoChunk(UnsafeChunk allayChunk) implements ProtoChunk {
public void setBlock(int x, int y, int z, @NotNull BlockState blockState) {
AllayBlockState allayBlockState = (AllayBlockState) blockState;
allayChunk.setBlockState(x, y, z, allayBlockState.allayBlockState());
boolean containsWater = allayBlockState.containsWater() || allayChunk.getBlockState(x, y, z).getBlockType().hasBlockTag(BlockTags.WATER);
if (containsWater) {
if(allayBlockState.containsWater() || allayChunk.getBlockState(x, y, z).getBlockType().hasBlockTag(BlockTags.WATER)) {
allayChunk.setBlockState(x, y, z, WATER, 1);
}
}

View File

@@ -41,10 +41,11 @@ public record AllayProtoWorld(AllayServerWorld allayServerWorld, OtherChunkAcces
@Override
public void setBlockState(int x, int y, int z, BlockState data, boolean physics) {
AllayBlockState allayBlockState = (AllayBlockState)data;
boolean containsWater = allayBlockState.containsWater() || context.getBlockState(x, y, z).getBlockType().hasBlockTag(BlockTags.WATER);
AllayBlockState allayBlockState = (AllayBlockState) data;
context.setBlockState(x, y, z, allayBlockState.allayBlockState());
if (containsWater) context.setBlockState(x, y, z, WATER, 1);
if(allayBlockState.containsWater() || context.getBlockState(x, y, z).getBlockType().hasBlockTag(BlockTags.WATER)) {
context.setBlockState(x, y, z, WATER, 1);
}
}
@Override

View File

@@ -1,7 +1,5 @@
package com.dfsek.terra.allay.delegate;
import org.allaymc.api.block.property.type.BlockPropertyTypes;
import org.allaymc.api.block.type.BlockTypes;
import org.allaymc.api.world.Dimension;
import com.dfsek.terra.allay.Mapping;
@@ -21,12 +19,9 @@ import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator;
* @author daoge_cmd
*/
public record AllayServerWorld(AllayGeneratorWrapper allayGeneratorWrapper, Dimension allayDimension) implements ServerWorld {
private static final org.allaymc.api.block.type.BlockState WATER = BlockTypes.WATER.ofState(BlockPropertyTypes.LIQUID_DEPTH.createValue(0));
@Override
public Chunk getChunkAt(int x, int z) {
return new AllayChunk(this, allayDimension.getChunkService().getChunk(x ,z));
return new AllayChunk(this, allayDimension.getChunkService().getChunk(x, z));
}
@Override

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,197 @@
{ "minecraft:badlands": { "bedrock_id": 37 }, "minecraft:bamboo_jungle": { "bedrock_id": 48 }, "minecraft:basalt_deltas": { "bedrock_id": 181 }, "minecraft:beach": { "bedrock_id": 16 }, "minecraft:birch_forest": { "bedrock_id": 27 }, "minecraft:cherry_grove": { "bedrock_id": 192 }, "minecraft:cold_ocean": { "bedrock_id": 44 }, "minecraft:crimson_forest": { "bedrock_id": 179 }, "minecraft:dark_forest": { "bedrock_id": 29 }, "minecraft:deep_cold_ocean": { "bedrock_id": 45 }, "minecraft:deep_dark": { "bedrock_id": 190 }, "minecraft:deep_frozen_ocean": { "bedrock_id": 47 }, "minecraft:deep_lukewarm_ocean": { "bedrock_id": 43 }, "minecraft:deep_ocean": { "bedrock_id": 24 }, "minecraft:desert": { "bedrock_id": 2 }, "minecraft:dripstone_caves": { "bedrock_id": 188 }, "minecraft:end_barrens": { "bedrock_id": 9 }, "minecraft:end_highlands": { "bedrock_id": 9 }, "minecraft:end_midlands": { "bedrock_id": 9 }, "minecraft:eroded_badlands": { "bedrock_id": 165 }, "minecraft:flower_forest": { "bedrock_id": 132 }, "minecraft:forest": { "bedrock_id": 4 }, "minecraft:frozen_ocean": { "bedrock_id": 46 }, "minecraft:frozen_peaks": { "bedrock_id": 183 }, "minecraft:frozen_river": { "bedrock_id": 11 }, "minecraft:grove": { "bedrock_id": 185 }, "minecraft:ice_spikes": { "bedrock_id": 140 }, "minecraft:jagged_peaks": { "bedrock_id": 182 }, "minecraft:jungle": { "bedrock_id": 21 }, "minecraft:lukewarm_ocean": { "bedrock_id": 42 }, "minecraft:lush_caves": { "bedrock_id": 187 }, "minecraft:mangrove_swamp": { "bedrock_id": 191 }, "minecraft:meadow": { "bedrock_id": 186 }, "minecraft:mushroom_fields": { "bedrock_id": 14 }, "minecraft:nether_wastes": { "bedrock_id": 8 }, "minecraft:ocean": { "bedrock_id": 0 }, "minecraft:old_growth_birch_forest": { "bedrock_id": 155 }, "minecraft:old_growth_pine_taiga": { "bedrock_id": 32 }, "minecraft:old_growth_spruce_taiga": { "bedrock_id": 160 }, "minecraft:plains": { "bedrock_id": 1 }, "minecraft:river": { "bedrock_id": 7 }, "minecraft:savanna": { "bedrock_id": 35 }, "minecraft:savanna_plateau": { "bedrock_id": 36 }, "minecraft:small_end_islands": { "bedrock_id": 9 }, "minecraft:snowy_beach": { "bedrock_id": 26 }, "minecraft:snowy_plains": { "bedrock_id": 12 }, "minecraft:snowy_slopes": { "bedrock_id": 184 }, "minecraft:snowy_taiga": { "bedrock_id": 30 }, "minecraft:soul_sand_valley": { "bedrock_id": 178 }, "minecraft:sparse_jungle": { "bedrock_id": 23 }, "minecraft:stony_peaks": { "bedrock_id": 189 }, "minecraft:stony_shore": { "bedrock_id": 25 }, "minecraft:sunflower_plains": { "bedrock_id": 129 }, "minecraft:swamp": { "bedrock_id": 6 }, "minecraft:taiga": { "bedrock_id": 5 }, "minecraft:the_end": { "bedrock_id": 9 }, "minecraft:the_void": { "bedrock_id": 7 }, "minecraft:warm_ocean": { "bedrock_id": 40 }, "minecraft:warped_forest": { "bedrock_id": 180 }, "minecraft:windswept_forest": { "bedrock_id": 34 }, "minecraft:windswept_gravelly_hills": { "bedrock_id": 131 }, "minecraft:windswept_hills": { "bedrock_id": 3 }, "minecraft:windswept_savanna": { "bedrock_id": 163 }, "minecraft:wooded_badlands": { "bedrock_id": 38 } }
{
"minecraft:badlands": {
"bedrock_id": 37
},
"minecraft:bamboo_jungle": {
"bedrock_id": 48
},
"minecraft:basalt_deltas": {
"bedrock_id": 181
},
"minecraft:beach": {
"bedrock_id": 16
},
"minecraft:birch_forest": {
"bedrock_id": 27
},
"minecraft:cherry_grove": {
"bedrock_id": 192
},
"minecraft:cold_ocean": {
"bedrock_id": 44
},
"minecraft:crimson_forest": {
"bedrock_id": 179
},
"minecraft:dark_forest": {
"bedrock_id": 29
},
"minecraft:deep_cold_ocean": {
"bedrock_id": 45
},
"minecraft:deep_dark": {
"bedrock_id": 190
},
"minecraft:deep_frozen_ocean": {
"bedrock_id": 47
},
"minecraft:deep_lukewarm_ocean": {
"bedrock_id": 43
},
"minecraft:deep_ocean": {
"bedrock_id": 24
},
"minecraft:desert": {
"bedrock_id": 2
},
"minecraft:dripstone_caves": {
"bedrock_id": 188
},
"minecraft:end_barrens": {
"bedrock_id": 9
},
"minecraft:end_highlands": {
"bedrock_id": 9
},
"minecraft:end_midlands": {
"bedrock_id": 9
},
"minecraft:eroded_badlands": {
"bedrock_id": 165
},
"minecraft:flower_forest": {
"bedrock_id": 132
},
"minecraft:forest": {
"bedrock_id": 4
},
"minecraft:frozen_ocean": {
"bedrock_id": 46
},
"minecraft:frozen_peaks": {
"bedrock_id": 183
},
"minecraft:frozen_river": {
"bedrock_id": 11
},
"minecraft:grove": {
"bedrock_id": 185
},
"minecraft:ice_spikes": {
"bedrock_id": 140
},
"minecraft:jagged_peaks": {
"bedrock_id": 182
},
"minecraft:jungle": {
"bedrock_id": 21
},
"minecraft:lukewarm_ocean": {
"bedrock_id": 42
},
"minecraft:lush_caves": {
"bedrock_id": 187
},
"minecraft:mangrove_swamp": {
"bedrock_id": 191
},
"minecraft:meadow": {
"bedrock_id": 186
},
"minecraft:mushroom_fields": {
"bedrock_id": 14
},
"minecraft:nether_wastes": {
"bedrock_id": 8
},
"minecraft:ocean": {
"bedrock_id": 0
},
"minecraft:old_growth_birch_forest": {
"bedrock_id": 155
},
"minecraft:old_growth_pine_taiga": {
"bedrock_id": 32
},
"minecraft:old_growth_spruce_taiga": {
"bedrock_id": 160
},
"minecraft:pale_garden": {
"bedrock_id": 62
},
"minecraft:plains": {
"bedrock_id": 1
},
"minecraft:river": {
"bedrock_id": 7
},
"minecraft:savanna": {
"bedrock_id": 35
},
"minecraft:savanna_plateau": {
"bedrock_id": 36
},
"minecraft:small_end_islands": {
"bedrock_id": 9
},
"minecraft:snowy_beach": {
"bedrock_id": 26
},
"minecraft:snowy_plains": {
"bedrock_id": 12
},
"minecraft:snowy_slopes": {
"bedrock_id": 184
},
"minecraft:snowy_taiga": {
"bedrock_id": 30
},
"minecraft:soul_sand_valley": {
"bedrock_id": 178
},
"minecraft:sparse_jungle": {
"bedrock_id": 23
},
"minecraft:stony_peaks": {
"bedrock_id": 189
},
"minecraft:stony_shore": {
"bedrock_id": 25
},
"minecraft:sunflower_plains": {
"bedrock_id": 129
},
"minecraft:swamp": {
"bedrock_id": 6
},
"minecraft:taiga": {
"bedrock_id": 5
},
"minecraft:the_end": {
"bedrock_id": 9
},
"minecraft:the_void": {
"bedrock_id": 7
},
"minecraft:warm_ocean": {
"bedrock_id": 40
},
"minecraft:warped_forest": {
"bedrock_id": 180
},
"minecraft:windswept_forest": {
"bedrock_id": 34
},
"minecraft:windswept_gravelly_hills": {
"bedrock_id": 131
},
"minecraft:windswept_hills": {
"bedrock_id": 3
},
"minecraft:windswept_savanna": {
"bedrock_id": 163
},
"minecraft:wooded_badlands": {
"bedrock_id": 38
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,17 @@
{
"entrance": "com.dfsek.terra.allay.TerraAllayPlugin",
"name": "Terra",
"authors": ["daoge_cmd", "dfsek", "duplexsystem", "Astrash", "solonovamax", "Sancires", "Aureus", "RogueShade"],
"authors": [
"daoge_cmd",
"IWareQ",
"dfsek",
"duplexsystem",
"Astrash",
"solonovamax",
"Sancires",
"Aureus",
"RogueShade"
],
"version": "@VERSION@",
"description": "@DESCRIPTION@",
"website": "@WIKI@"

View File

@@ -54,6 +54,9 @@ public interface Initializer {
private static Initializer constructInitializer() {
try {
String packageVersion = NMS;
if (NMS.equals("v1_21_4")) {
packageVersion = "v1_21_3";
}
Class<?> initializerClass = Class.forName(TERRA_PACKAGE + "." + packageVersion + ".NMSInitializer");
try {

View File

@@ -2,12 +2,12 @@ package com.dfsek.terra.bukkit.nms.v1_21_3;
import com.dfsek.terra.bukkit.nms.v1_21_3.config.VanillaBiomeProperties;
import com.google.common.collect.ImmutableMap;
import net.minecraft.core.Holder;
import net.minecraft.core.Holder.Reference;
import net.minecraft.core.HolderSet;
import net.minecraft.core.HolderSet.Named;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.RegistrationInfo;
import net.minecraft.core.WritableRegistry;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
@@ -19,8 +19,11 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.dfsek.terra.bukkit.world.BukkitPlatformBiome;
import com.dfsek.terra.registry.master.ConfigRegistry;
@@ -34,10 +37,12 @@ public class AwfulBukkitHacks {
public static void registerBiomes(ConfigRegistry configRegistry) {
try {
LOGGER.info("Hacking biome registry...");
WritableRegistry<Biome> biomeRegistry = (WritableRegistry<Biome>) RegistryFetcher.biomeRegistry();
MappedRegistry<Biome> biomeRegistry = (MappedRegistry<Biome>) RegistryFetcher.biomeRegistry();
Reflection.MAPPED_REGISTRY.setFrozen((MappedRegistry<?>) biomeRegistry, false);
// Unfreeze the biome registry to allow modification
Reflection.MAPPED_REGISTRY.setFrozen(biomeRegistry, false);
// Register the terra biomes to the registry
configRegistry.forEach(pack -> pack.getRegistry(com.dfsek.terra.api.world.biome.Biome.class).forEach((key, biome) -> {
try {
BukkitPlatformBiome platformBiome = (BukkitPlatformBiome) biome.getPlatformBiome();
@@ -67,7 +72,7 @@ public class AwfulBukkitHacks {
}
}));
Reflection.MAPPED_REGISTRY.setFrozen((MappedRegistry<?>) biomeRegistry, true); // freeze registry again :)
Reflection.MAPPED_REGISTRY.setFrozen(biomeRegistry, true); // freeze registry again :)
LOGGER.info("Doing tag garbage....");
Map<TagKey<Biome>, List<Holder<Biome>>> collect = biomeRegistry
@@ -95,12 +100,51 @@ public class AwfulBukkitHacks {
() -> LOGGER.error("No such biome: {}", tb))),
() -> LOGGER.error("No vanilla biome: {}", vb)));
((MappedRegistry<Biome>) biomeRegistry).bindAllTagsToEmpty();
ImmutableMap.copyOf(collect).forEach(biomeRegistry::bindTag);
resetTags(biomeRegistry);
bindTags(biomeRegistry, collect);
} catch(SecurityException | IllegalArgumentException exception) {
throw new RuntimeException(exception);
}
}
private static <T> void bindTags(MappedRegistry<T> registry, Map<TagKey<T>, List<Holder<T>>> tagEntries) {
Map<Holder.Reference<T>, List<TagKey<T>>> map = new IdentityHashMap<>();
Reflection.MAPPED_REGISTRY.getByKey(registry).values().forEach(entry -> map.put(entry, new ArrayList<>()));
tagEntries.forEach((tag, entries) -> {
for (Holder<T> holder : entries) {
// if (!holder.canSerializeIn(registry.asLookup())) {
// throw new IllegalStateException("Can't create named set " + tag + " containing value " + holder + " from outside registry " + this);
// }
if (!(holder instanceof Holder.Reference<T> reference)) {
throw new IllegalStateException("Found direct holder " + holder + " value in tag " + tag);
}
map.get(reference).add(tag);
}
});
// Set<TagKey<T>> set = Sets.difference(registry.tags.keySet(), tagEntries.keySet());
// if (!set.isEmpty()) {
// LOGGER.warn(
// "Not all defined tags for registry {} are present in data pack: {}",
// registry.key(),
// set.stream().map(tag -> tag.location().toString()).sorted().collect(Collectors.joining(", "))
// );
// }
Map<TagKey<T>, HolderSet.Named<T>> map2 = new IdentityHashMap<>(registry.getTags().collect(Collectors.toMap(
Named::key,
(named) -> named
)));
tagEntries.forEach((tag, entries) -> Reflection.HOLDER_SET.invokeBind(map2.computeIfAbsent(tag, key -> Reflection.MAPPED_REGISTRY.invokeCreateTag(registry, key)), entries));
map.forEach(Reflection.HOLDER_REFERENCE::invokeBindTags);
Reflection.MAPPED_REGISTRY.setAllTags(registry, Reflection.MAPPED_REGISTRY_TAG_SET.invokeFromMap(map2));
}
private static void resetTags(MappedRegistry<?> registry) {
registry.getTags().forEach(entryList -> Reflection.HOLDER_SET.invokeBind(entryList, List.of()));
Reflection.MAPPED_REGISTRY.getByKey(registry).values().forEach(entry -> Reflection.HOLDER_REFERENCE.invokeBindTags(entry, Set.of()));
}
}

View File

@@ -32,9 +32,21 @@ public class NMSBiomeInjector {
.waterColor(Objects.requireNonNullElse(vanillaBiomeProperties.getWaterColor(), vanilla.getWaterColor()))
.waterFogColor(Objects.requireNonNullElse(vanillaBiomeProperties.getWaterFogColor(), vanilla.getWaterFogColor()))
.skyColor(Objects.requireNonNullElse(vanillaBiomeProperties.getSkyColor(), vanilla.getSkyColor()))
.grassColorModifier(Objects.requireNonNullElse(vanillaBiomeProperties.getGrassColorModifier(), vanilla.getSpecialEffects().getGrassColorModifier()))
.grassColorOverride(Objects.requireNonNullElse(vanillaBiomeProperties.getGrassColor(), vanilla.getSpecialEffects().getGrassColorOverride().orElseGet(() -> Reflection.BIOME.invokeGrassColorFromTexture(vanilla))))
.foliageColorOverride(Objects.requireNonNullElse(vanillaBiomeProperties.getFoliageColor(), vanilla.getFoliageColor()));
.grassColorModifier(Objects.requireNonNullElse(vanillaBiomeProperties.getGrassColorModifier(), vanilla.getSpecialEffects().getGrassColorModifier()));
// .grassColorOverride(Objects.requireNonNullElse(vanillaBiomeProperties.getGrassColor(), vanilla.getSpecialEffects().getGrassColorOverride().orElseGet(() -> Reflection.BIOME.invokeGrassColorFromTexture(vanilla))))
// .foliageColorOverride(Objects.requireNonNullElse(vanillaBiomeProperties.getFoliageColor(), vanilla.getFoliageColor()));
if(vanillaBiomeProperties.getGrassColor() == null) {
vanilla.getSpecialEffects().getGrassColorOverride().ifPresent(effects::grassColorOverride);
} else {
effects.grassColorOverride(vanillaBiomeProperties.getGrassColor());
}
if(vanillaBiomeProperties.getFoliageColor() == null) {
vanilla.getSpecialEffects().getFoliageColorOverride().ifPresent(effects::foliageColorOverride);
} else {
effects.foliageColorOverride(vanillaBiomeProperties.getFoliageColor());
}
if(vanillaBiomeProperties.getParticleConfig() == null) {
vanilla.getSpecialEffects().getAmbientParticleSettings().ifPresent(effects::ambientParticle);

View File

@@ -4,7 +4,9 @@ import net.minecraft.core.Holder;
import net.minecraft.core.Holder.Reference;
import net.minecraft.core.HolderSet;
import net.minecraft.core.MappedRegistry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ChunkMap;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.biome.Biome;
@@ -15,19 +17,24 @@ import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldGetter;
import xyz.jpenilla.reflectionremapper.proxy.annotation.FieldSetter;
import xyz.jpenilla.reflectionremapper.proxy.annotation.MethodName;
import xyz.jpenilla.reflectionremapper.proxy.annotation.Proxies;
import xyz.jpenilla.reflectionremapper.proxy.annotation.Static;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class Reflection {
public static final MappedRegistryProxy MAPPED_REGISTRY;
public static final MappedRegistryTagSetProxy MAPPED_REGISTRY_TAG_SET;
public static final StructureManagerProxy STRUCTURE_MANAGER;
public static final ReferenceProxy REFERENCE;
public static final ChunkMapProxy CHUNKMAP;
public static final HolderSetProxy HOLDER_SET;
public static final HolderReferenceProxy HOLDER_REFERENCE;
public static final HolderSetNamedProxy HOLDER_SET;
public static final BiomeProxy BIOME;
static {
@@ -36,18 +43,36 @@ public class Reflection {
Reflection.class.getClassLoader());
MAPPED_REGISTRY = reflectionProxyFactory.reflectionProxy(MappedRegistryProxy.class);
MAPPED_REGISTRY_TAG_SET = reflectionProxyFactory.reflectionProxy(MappedRegistryTagSetProxy.class);
STRUCTURE_MANAGER = reflectionProxyFactory.reflectionProxy(StructureManagerProxy.class);
REFERENCE = reflectionProxyFactory.reflectionProxy(ReferenceProxy.class);
CHUNKMAP = reflectionProxyFactory.reflectionProxy(ChunkMapProxy.class);
HOLDER_SET = reflectionProxyFactory.reflectionProxy(HolderSetProxy.class);
HOLDER_REFERENCE = reflectionProxyFactory.reflectionProxy(HolderReferenceProxy.class);
HOLDER_SET = reflectionProxyFactory.reflectionProxy(HolderSetNamedProxy.class);
BIOME = reflectionProxyFactory.reflectionProxy(BiomeProxy.class);
}
@Proxies(MappedRegistry.class)
public interface MappedRegistryProxy {
@FieldGetter("byKey")
<T> Map<ResourceKey<T>, Reference<T>> getByKey(MappedRegistry<T> instance);
@FieldSetter("allTags")
<T> void setAllTags(MappedRegistry<T> instance, Object obj);
@FieldSetter("frozen")
void setFrozen(MappedRegistry<?> instance, boolean frozen);
@MethodName("createTag")
<T> HolderSet.Named<T> invokeCreateTag(MappedRegistry<T> instance, TagKey<T> tag);
}
@Proxies(className = "net.minecraft.core.MappedRegistry$TagSet")
public interface MappedRegistryTagSetProxy {
@MethodName("fromMap")
@Static
<T> Object invokeFromMap(Map<TagKey<T>, HolderSet.Named<T>> map);
}
@@ -62,6 +87,9 @@ public class Reflection {
public interface ReferenceProxy {
@MethodName("bindValue")
<T> void invokeBindValue(Reference<T> instance, T value);
@MethodName("bindTags")
<T> void invokeBindTags(Reference<T> instance, Collection<TagKey<T>> tags);
}
@Proxies(ChunkMap.class)
@@ -73,10 +101,19 @@ public class Reflection {
void setWorldGenContext(ChunkMap instance, WorldGenContext worldGenContext);
}
@Proxies(HolderSet.class)
public interface HolderSetProxy {
@Proxies(Holder.Reference.class)
public interface HolderReferenceProxy {
@MethodName("bindTags")
<T> void invokeBindTags(Holder.Reference<T> instance, Collection<TagKey<T>> tags);
}
@Proxies(HolderSet.Named.class)
public interface HolderSetNamedProxy {
@MethodName("bind")
<T> void invokeBind(HolderSet.Named<T> instance, List<Holder<T>> entries);
@MethodName("contents")
<T> List<Holder<T>> invokeContents(HolderSet<T> instance);
<T> List<Holder<T>> invokeContents(HolderSet.Named<T> instance);
}
@Proxies(Biome.class)

View File

@@ -3,22 +3,16 @@ package com.dfsek.terra.bukkit.nms.v1_21_3;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.MinecraftServer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.level.biome.Biome;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.CraftServer;
public class RegistryFetcher {
private static <T> Registry<T> getRegistry(ResourceKey<Registry<T>> key) {
CraftServer craftserver = (CraftServer) Bukkit.getServer();
DedicatedServer dedicatedserver = craftserver.getServer();
return dedicatedserver
return MinecraftServer.getServer()
.registryAccess()
.get(key)
.orElseThrow()
.value();
.lookupOrThrow(key);
}
public static Registry<Biome> biomeRegistry() {

View File

@@ -32,9 +32,9 @@
"terra.common.mixins.json"
],
"depends": {
"fabricloader": ">=0.16.7",
"fabricloader": ">=0.16.10",
"java": ">=21",
"minecraft": ">=1.21.3",
"minecraft": ">=1.21.4",
"fabric": "*"
}
}

View File

@@ -0,0 +1,11 @@
dependencies {
shadedApi(project(":common:implementation:base"))
shadedApi("com.github.ben-manes.caffeine", "caffeine", Versions.Libraries.caffeine)
shadedImplementation("com.google.guava", "guava", Versions.Libraries.Internal.guava)
compileOnly("net.minestom", "minestom-snapshots", Versions.Minestom.minestom)
}
tasks.named("jar") {
finalizedBy("installAddonsIntoDefaultJar")
}

View File

@@ -0,0 +1,28 @@
plugins {
application
}
val javaMainClass = "com.dfsek.terra.minestom.TerraMinestomExample"
dependencies {
shadedApi(project(":platforms:minestom"))
implementation("net.minestom", "minestom-snapshots", Versions.Minestom.minestom)
implementation("org.slf4j", "slf4j-simple", Versions.Libraries.slf4j)
}
tasks.withType<Jar> {
entryCompression = ZipEntryCompression.STORED
manifest {
attributes(
"Main-Class" to javaMainClass,
)
}
}
application {
mainClass.set(javaMainClass)
}
tasks.getByName("run").setProperty("workingDir", file("./run"))
addonDir(project.file("./run/terra/addons"), tasks.named("run").get())

View File

@@ -0,0 +1,132 @@
package com.dfsek.terra.minestom;
import net.kyori.adventure.text.Component;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.builder.Command;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode;
import net.minestom.server.event.player.AsyncPlayerConfigurationEvent;
import net.minestom.server.event.player.PlayerSpawnEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.LightingChunk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;
import com.dfsek.terra.minestom.world.TerraMinestomWorld;
import com.dfsek.terra.minestom.world.TerraMinestomWorldBuilder;
public class TerraMinestomExample {
private static final Logger logger = LoggerFactory.getLogger(TerraMinestomExample.class);
private final MinecraftServer server = MinecraftServer.init();
private Instance instance;
private TerraMinestomWorld world;
public void createNewInstance() {
instance = MinecraftServer.getInstanceManager().createInstanceContainer();
instance.setChunkSupplier(LightingChunk::new);
}
public void attachTerra() {
world = TerraMinestomWorldBuilder.from(instance)
.defaultPack()
.attach();
}
private void sendProgressBar(int current, int max) {
String left = "#".repeat((int) ((((float) current) / max) * 20));
String right = ".".repeat(20 - left.length());
int percent = (int) (((float) current) / max * 100);
String percentString = percent + "%";
percentString = " ".repeat(4 - percentString.length()) + percentString;
String message = percentString + " |" + left + right + "| " + current + "/" + max;
logger.info(message);
}
public void preloadWorldAndMeasure() {
int radius = 12;
int chunksLoading = (radius * 2 + 1) * (radius * 2 + 1);
AtomicInteger chunksLeft = new AtomicInteger(chunksLoading);
long start = System.nanoTime();
for(int x = -radius; x <= radius; x++) {
for(int z = -radius; z <= radius; z++) {
instance.loadChunk(x, z).thenAccept(chunk -> {
int left = chunksLeft.decrementAndGet();
if(left == 0) {
long end = System.nanoTime();
sendProgressBar(chunksLoading - left, chunksLoading);
double chunksPerSecond = chunksLoading / ((end - start) / 1000000000.0);
logger.info(
"Preloaded {} chunks in world in {}ms. That's {} Chunks/s",
chunksLoading,
(end - start) / 1000000.0,
chunksPerSecond
);
world.displayStats();
} else if(left % 60 == 0) {
sendProgressBar(chunksLoading - left, chunksLoading);
}
});
}
}
}
public void addListeners() {
MinecraftServer.getGlobalEventHandler().addListener(AsyncPlayerConfigurationEvent.class, event -> {
event.setSpawningInstance(instance);
event.getPlayer().setRespawnPoint(new Pos(0.0, 100.0, 0.0));
});
MinecraftServer.getGlobalEventHandler().addListener(PlayerSpawnEvent.class, event -> {
event.getPlayer().setGameMode(GameMode.SPECTATOR);
});
}
public void addScheduler() {
MinecraftServer.getSchedulerManager().buildTask(() -> world.displayStats())
.repeat(Duration.ofSeconds(10))
.schedule();
}
public void addCommands() {
MinecraftServer.getCommandManager().register(new RegenerateCommand());
}
public void bind() {
logger.info("Starting server on port 25565");
server.start("localhost", 25565);
}
public static void main(String[] args) {
TerraMinestomExample example = new TerraMinestomExample();
example.createNewInstance();
example.attachTerra();
example.preloadWorldAndMeasure();
example.addScheduler();
example.addListeners();
example.addCommands();
example.bind();
}
public class RegenerateCommand extends Command {
public RegenerateCommand() {
super("regenerate");
setDefaultExecutor((sender, context) -> regenerate());
}
private void regenerate() {
instance.sendMessage(Component.text("Regenerating world"));
createNewInstance();
attachTerra();
preloadWorldAndMeasure();
MinecraftServer.getConnectionManager().getOnlinePlayers().forEach(player ->
player.setInstance(instance, new Pos(0, 100, 0))
);
}
}
}

View File

@@ -0,0 +1,17 @@
package com.dfsek.terra.minestom;
import com.dfsek.terra.api.util.vector.Vector3;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
public class MinestomAdapter {
public static Vector3 adapt(Point point) {
return Vector3.of(point.x(), point.y(), point.z());
}
public static Pos adapt(Vector3 vector) {
return new Pos(vector.getX(), vector.getY(), vector.getZ());
}
}

View File

@@ -0,0 +1,94 @@
package com.dfsek.terra.minestom;
import com.dfsek.tectonic.api.TypeRegistry;
import com.dfsek.tectonic.api.loader.type.TypeLoader;
import com.dfsek.terra.AbstractPlatform;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.entity.EntityType;
import com.dfsek.terra.api.event.events.platform.PlatformInitializationEvent;
import com.dfsek.terra.api.handle.ItemHandle;
import com.dfsek.terra.api.handle.WorldHandle;
import com.dfsek.terra.api.world.biome.PlatformBiome;
import com.dfsek.terra.minestom.biome.MinestomBiomeLoader;
import com.dfsek.terra.minestom.entity.MinestomEntityType;
import com.dfsek.terra.minestom.item.MinestomItemHandle;
import com.dfsek.terra.minestom.world.MinestomChunkGeneratorWrapper;
import com.dfsek.terra.minestom.world.MinestomWorldHandle;
import net.minestom.server.MinecraftServer;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
public final class MinestomPlatform extends AbstractPlatform {
private static final Logger LOGGER = LoggerFactory.getLogger(MinestomPlatform.class);
private static MinestomPlatform INSTANCE = null;
private final MinestomWorldHandle worldHandle = new MinestomWorldHandle();
private final MinestomItemHandle itemHandle = new MinestomItemHandle();
private MinestomPlatform() {
load();
getEventManager().callEvent(new PlatformInitializationEvent());
}
@Override
public void register(TypeRegistry registry) {
super.register(registry);
registry
.registerLoader(PlatformBiome.class, new MinestomBiomeLoader())
.registerLoader(EntityType.class, (TypeLoader<EntityType>) (annotatedType, o, configLoader, depthTracker) -> new MinestomEntityType((String) o))
.registerLoader(BlockState.class, (TypeLoader<BlockState>) (annotatedType, o, configLoader, depthTracker) -> worldHandle.createBlockState((String) o));
}
@Override
public boolean reload() {
getTerraConfig().load(this);
getRawConfigRegistry().clear();
boolean succeed = getRawConfigRegistry().loadAll(this);
MinecraftServer.getInstanceManager().getInstances().forEach(world -> {
if(world.generator() instanceof MinestomChunkGeneratorWrapper wrapper) {
getConfigRegistry().get(wrapper.getPack().getRegistryKey()).ifPresent(pack -> {
wrapper.setPack(pack);
LOGGER.info("Replaced pack in chunk generator for instance {}", world.getUniqueId());
});
}
});
return succeed;
}
@Override
public @NotNull WorldHandle getWorldHandle() {
return worldHandle;
}
@Override
public @NotNull ItemHandle getItemHandle() {
return itemHandle;
}
@Override
public @NotNull String platformName() {
return "Minestom";
}
@Override
public @NotNull File getDataFolder() {
String pathName = System.getProperty("terra.datafolder");
if (pathName == null) pathName = "./terra/";
File file = new File(pathName);
if(!file.exists()) file.mkdirs();
return file;
}
public static MinestomPlatform getInstance() {
if(INSTANCE == null) {
INSTANCE = new MinestomPlatform();
}
return INSTANCE;
}
}

View File

@@ -0,0 +1,16 @@
package com.dfsek.terra.minestom.api;
import com.dfsek.terra.api.block.entity.BlockEntity;
import net.minestom.server.coordinate.BlockVec;
import org.jetbrains.annotations.Nullable;
/**
* Represents a factory interface for creating instances of BlockEntity
* at a specified BlockVec position. This is not implemented directly because
* Minestom does not define a way to build block entities out of the box.
*/
public interface BlockEntityFactory {
@Nullable BlockEntity createBlockEntity(BlockVec position);
}

View File

@@ -0,0 +1,13 @@
package com.dfsek.terra.minestom.api;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
/**
* Allows adding AI to generated entities using custom entity types
*/
public interface EntityFactory {
Entity createEntity(EntityType type);
}

View File

@@ -0,0 +1,17 @@
package com.dfsek.terra.minestom.biome;
import com.dfsek.terra.api.world.biome.PlatformBiome;
import net.minestom.server.world.biome.Biome;
public class MinestomBiome implements PlatformBiome {
private final Biome biome;
public MinestomBiome(Biome biome) { this.biome = biome; }
@Override
public Biome getHandle() {
return biome;
}
}

View File

@@ -0,0 +1,31 @@
package com.dfsek.terra.minestom.biome;
import com.dfsek.tectonic.api.depth.DepthTracker;
import com.dfsek.tectonic.api.exception.LoadException;
import com.dfsek.tectonic.api.loader.ConfigLoader;
import com.dfsek.tectonic.api.loader.type.TypeLoader;
import com.dfsek.terra.api.world.biome.PlatformBiome;
import net.minestom.server.MinecraftServer;
import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.world.biome.Biome;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.AnnotatedType;
public class MinestomBiomeLoader implements TypeLoader<PlatformBiome> {
private final DynamicRegistry<Biome> biomeRegistry = MinecraftServer.getBiomeRegistry();
@Override
public PlatformBiome load(@NotNull AnnotatedType annotatedType, @NotNull Object o, @NotNull ConfigLoader configLoader,
DepthTracker depthTracker) throws LoadException {
String id = (String) o;
NamespaceID biomeID = NamespaceID.from(id);
Biome biome = biomeRegistry.get(biomeID);
if(biome == null) throw new LoadException("Biome %s does not exist in registry".formatted(id), depthTracker);
return new MinestomBiome(biome);
}
}

View File

@@ -0,0 +1,14 @@
package com.dfsek.terra.minestom.block;
import com.dfsek.terra.api.block.entity.BlockEntity;
import com.dfsek.terra.minestom.api.BlockEntityFactory;
import net.minestom.server.coordinate.BlockVec;
public class DefaultBlockEntityFactory implements BlockEntityFactory {
@Override
public BlockEntity createBlockEntity(BlockVec position) {
return null;
}
}

View File

@@ -0,0 +1,98 @@
package com.dfsek.terra.minestom.block;
import com.dfsek.terra.api.block.BlockType;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.block.state.properties.Property;
import net.minestom.server.instance.block.Block;
import java.util.HashMap;
import java.util.Objects;
import java.util.stream.Collectors;
public class MinestomBlockState implements BlockState {
private final Block block;
public MinestomBlockState(Block block) {
if(block == null) {
this.block = Block.AIR;
} else {
this.block = block;
}
}
public MinestomBlockState(String data) {
if(!data.contains("[")) {
block = Block.fromNamespaceId(data);
return;
}
String[] split = data.split("\\[");
String namespaceId = split[0];
String properties = split[1].substring(0, split[1].length() - 1);
Block block = Block.fromNamespaceId(namespaceId);
HashMap<String, String> propertiesMap = new HashMap<>();
for(String property : properties.split(",")) {
String[] kv = property.split("=");
propertiesMap.put(kv[0].strip(), kv[1].strip());
}
assert block != null;
this.block = block.withProperties(propertiesMap);
}
@Override
public boolean matches(BlockState other) {
return ((MinestomBlockState) other).block.compare(block);
}
@Override
public <T extends Comparable<T>> boolean has(Property<T> property) {
return false;
}
@Override
public <T extends Comparable<T>> T get(Property<T> property) {
return null;
}
@Override
public <T extends Comparable<T>> BlockState set(Property<T> property, T value) {
return null;
}
@Override
public BlockType getBlockType() {
return new MinestomBlockType(block);
}
@Override
public String getAsString(boolean properties) {
String name = block.namespace().asString();
if(!properties || block.properties().isEmpty()) {
return name;
}
name += "[" + block.properties().entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(
Collectors.joining(",")) + "]";
return name;
}
@Override
public boolean isAir() {
return block.isAir();
}
@Override
public Object getHandle() {
return block;
}
@Override
public int hashCode() {
return Objects.hashCode(block.id());
}
}

View File

@@ -0,0 +1,48 @@
package com.dfsek.terra.minestom.block;
import com.dfsek.terra.api.block.BlockType;
import com.dfsek.terra.api.block.state.BlockState;
import net.minestom.server.instance.block.Block;
public class MinestomBlockType implements BlockType {
private final Block block;
public MinestomBlockType(Block block) {
this.block = block;
}
@Override
public BlockState getDefaultState() {
return new MinestomBlockState(block);
}
@Override
public boolean isSolid() {
return block.isSolid();
}
@Override
public boolean isWater() {
return block.isLiquid();
}
@Override
public Object getHandle() {
return block;
}
@Override
public int hashCode() {
return block.id();
}
@Override
public boolean equals(Object obj) {
if(obj instanceof MinestomBlockType other) {
return block.id() == other.block.id();
}
return false;
}
}

View File

@@ -0,0 +1,58 @@
package com.dfsek.terra.minestom.chunk;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.world.chunk.generation.ProtoChunk;
import com.dfsek.terra.minestom.block.MinestomBlockState;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.generator.UnitModifier;
import org.jetbrains.annotations.NotNull;
public class CachedChunk implements ProtoChunk {
private final int minHeight;
private final int maxHeight;
private final Block[][][] blocks;
public CachedChunk(int minHeight, int maxHeight) {
this.minHeight = minHeight;
this.maxHeight = maxHeight;
this.blocks = new Block[16][maxHeight - minHeight + 1][16];
for(int x = 0; x < 16; x++) {
for(int z = 0; z < 16; z++) {
for(int y = 0; y < maxHeight - minHeight + 1; y++) {
blocks[x][y][z] = Block.AIR;
}
}
}
}
public void writeRelative(UnitModifier modifier) {
modifier.setAllRelative((x, y, z) -> blocks[x][y][z]);
}
@Override
public void setBlock(int x, int y, int z, @NotNull BlockState blockState) {
Block block = (Block) blockState.getHandle();
if(block == null) return;
blocks[x][y - minHeight][z] = block;
}
@Override
public @NotNull BlockState getBlock(int x, int y, int z) {
return new MinestomBlockState(blocks[x][y - minHeight][z]);
}
@Override
public Object getHandle() {
return blocks;
}
@Override
public int getMaxHeight() {
return maxHeight;
}
}

View File

@@ -0,0 +1,49 @@
package com.dfsek.terra.minestom.chunk;
import com.dfsek.terra.api.util.generic.pair.Pair;
import com.dfsek.terra.api.world.ServerWorld;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import net.minestom.server.world.DimensionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GeneratedChunkCache {
private static final Logger log = LoggerFactory.getLogger(GeneratedChunkCache.class);
private final LoadingCache<Pair<Integer, Integer>, CachedChunk> cache;
private final DimensionType dimensionType;
private final ChunkGenerator generator;
private final ServerWorld world;
private final BiomeProvider biomeProvider;
public GeneratedChunkCache(DimensionType dimensionType, ChunkGenerator generator, ServerWorld world) {
this.dimensionType = dimensionType;
this.generator = generator;
this.world = world;
this.biomeProvider = world.getBiomeProvider();
this.cache = Caffeine.newBuilder().maximumSize(128).recordStats().build(
(Pair<Integer, Integer> key) -> generateChunk(key.getLeft(), key.getRight()));
}
private CachedChunk generateChunk(int x, int z) {
CachedChunk chunk = new CachedChunk(dimensionType.minY(), dimensionType.maxY());
generator.generateChunkData(chunk, world, biomeProvider, x, z);
return chunk;
}
public void displayStats() {
CacheStats stats = cache.stats();
log.info("Avg load time: {}ms | Hit rate: {}% | Load Count: {}", stats.averageLoadPenalty(), stats.hitRate() * 100,
stats.loadCount());
}
public CachedChunk at(int x, int z) {
return cache.get(Pair.of(x, z));
}
}

View File

@@ -0,0 +1,51 @@
package com.dfsek.terra.minestom.chunk;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.world.ServerWorld;
import com.dfsek.terra.api.world.chunk.Chunk;
import com.dfsek.terra.minestom.block.MinestomBlockState;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.NotNull;
public class TerraMinestomChunk implements Chunk {
private net.minestom.server.instance.Chunk delegate;
private final ServerWorld world;
public TerraMinestomChunk(net.minestom.server.instance.Chunk delegate, ServerWorld world) {
this.delegate = delegate;
this.world = world;
}
@Override
public void setBlock(int x, int y, int z, BlockState data, boolean physics) {
delegate.setBlock(x, y, z, (Block) data.getHandle());
}
@Override
public @NotNull BlockState getBlock(int x, int y, int z) {
return new MinestomBlockState(delegate.getBlock(x, y, z));
}
@Override
public int getX() {
return delegate.getChunkX();
}
@Override
public int getZ() {
return delegate.getChunkZ();
}
@Override
public ServerWorld getWorld() {
return world;
}
@Override
public Object getHandle() {
return delegate;
}
}

View File

@@ -0,0 +1,14 @@
package com.dfsek.terra.minestom.entity;
import com.dfsek.terra.minestom.api.EntityFactory;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
public class DefaultEntityFactory implements EntityFactory {
@Override
public Entity createEntity(EntityType type) {
return new Entity(type);
}
}

View File

@@ -0,0 +1,68 @@
package com.dfsek.terra.minestom.entity;
import com.dfsek.terra.api.entity.Entity;
import com.dfsek.terra.api.entity.EntityType;
import com.dfsek.terra.api.util.vector.Vector3;
import com.dfsek.terra.api.world.ServerWorld;
import com.dfsek.terra.minestom.world.TerraMinestomWorld;
import net.minestom.server.coordinate.Pos;
public class DeferredMinestomEntity implements Entity {
private final EntityType type;
private double x;
private double y;
private double z;
private TerraMinestomWorld world;
public DeferredMinestomEntity(double x, double y, double z, EntityType type, TerraMinestomWorld world) {
this.x = x;
this.y = y;
this.z = z;
this.type = type;
this.world = world;
}
@Override
public Vector3 position() {
return Vector3.of(x, y, z);
}
public Pos pos() {
return new Pos(x, y, z);
}
@Override
public void position(Vector3 position) {
x = position.getX();
y = position.getY();
z = position.getZ();
}
@Override
public void world(ServerWorld world) {
this.world = (TerraMinestomWorld) world;
}
@Override
public ServerWorld world() {
return world;
}
@Override
public Object getHandle() {
return this;
}
public void spawn() {
int chunkX = (int) x >> 4;
int chunkZ = (int) z >> 4;
if(!world.getHandle().isChunkLoaded(chunkX, chunkZ)) {
return;
}
MinestomEntity.spawn(x, y, z, type, world);
}
}

View File

@@ -0,0 +1,55 @@
package com.dfsek.terra.minestom.entity;
import com.dfsek.terra.api.entity.EntityType;
import com.dfsek.terra.api.util.vector.Vector3;
import com.dfsek.terra.api.world.ServerWorld;
import com.dfsek.terra.minestom.MinestomAdapter;
import com.dfsek.terra.minestom.world.TerraMinestomWorld;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.instance.Instance;
public class MinestomEntity implements com.dfsek.terra.api.entity.Entity {
private final Entity delegate;
private final TerraMinestomWorld world;
public MinestomEntity(Entity delegate, TerraMinestomWorld world) {
this.delegate = delegate;
this.world = world;
}
@Override
public Vector3 position() {
return MinestomAdapter.adapt(delegate.getPosition());
}
@Override
public void position(Vector3 position) {
delegate.teleport(MinestomAdapter.adapt(position));
}
@Override
public void world(ServerWorld world) {
delegate.setInstance(((TerraMinestomWorld) world).getHandle());
}
@Override
public ServerWorld world() {
return world;
}
@Override
public Object getHandle() {
return delegate;
}
public static MinestomEntity spawn(double x, double y, double z, EntityType type, TerraMinestomWorld world) {
Instance instance = world.getHandle();
Entity entity = world.getEntityFactory().createEntity(((MinestomEntityType) type).getHandle());
entity.setInstance(instance, new Pos(x, y, z));
return new MinestomEntity(entity, world);
}
}

View File

@@ -0,0 +1,18 @@
package com.dfsek.terra.minestom.entity;
import net.minestom.server.entity.EntityType;
public class MinestomEntityType implements com.dfsek.terra.api.entity.EntityType {
private final EntityType delegate;
public MinestomEntityType(String id) {
delegate = EntityType.fromNamespaceId(id);
}
@Override
public EntityType getHandle() {
return delegate;
}
}

View File

@@ -0,0 +1,51 @@
package com.dfsek.terra.minestom.item;
import com.dfsek.terra.api.inventory.ItemStack;
import com.dfsek.terra.api.inventory.item.Enchantment;
import net.minestom.server.MinecraftServer;
import net.minestom.server.item.Material;
import net.minestom.server.utils.NamespaceID;
import java.util.Objects;
public class MinestomEnchantment implements Enchantment {
private final net.minestom.server.item.enchant.Enchantment delegate;
private final String id;
public MinestomEnchantment(net.minestom.server.item.enchant.Enchantment delegate) {
this.delegate = delegate;
id = Objects.requireNonNull(delegate.registry()).raw();
}
public MinestomEnchantment(String id) {
this.delegate = MinecraftServer.getEnchantmentRegistry().get(NamespaceID.from(id));
this.id = id;
}
@Override
public boolean canEnchantItem(ItemStack itemStack) {
return delegate.supportedItems().contains((Material) itemStack.getType().getHandle());
}
@Override
public boolean conflictsWith(Enchantment other) {
return delegate.exclusiveSet().contains(NamespaceID.from(((MinestomEnchantment) other).id));
}
@Override
public String getID() {
return id;
}
@Override
public int getMaxLevel() {
return delegate.maxLevel();
}
@Override
public net.minestom.server.item.enchant.Enchantment getHandle() {
return delegate;
}
}

View File

@@ -0,0 +1,28 @@
package com.dfsek.terra.minestom.item;
import com.dfsek.terra.api.handle.ItemHandle;
import com.dfsek.terra.api.inventory.Item;
import com.dfsek.terra.api.inventory.item.Enchantment;
import net.minestom.server.MinecraftServer;
import java.util.Set;
import java.util.stream.Collectors;
public class MinestomItemHandle implements ItemHandle {
@Override
public Item createItem(String data) {
return new MinestomMaterial(data);
}
@Override
public Enchantment getEnchantment(String id) {
return new MinestomEnchantment(id);
}
@Override
public Set<Enchantment> getEnchantments() {
return MinecraftServer.getEnchantmentRegistry().values().stream().map(MinestomEnchantment::new).collect(Collectors.toSet());
}
}

View File

@@ -0,0 +1,31 @@
package com.dfsek.terra.minestom.item;
import com.dfsek.terra.api.inventory.item.Enchantment;
import com.dfsek.terra.api.inventory.item.ItemMeta;
import java.util.HashMap;
import java.util.Map;
public class MinestomItemMeta implements ItemMeta {
private final HashMap<Enchantment, Integer> enchantments;
public MinestomItemMeta(HashMap<Enchantment, Integer> enchantments) {
this.enchantments = enchantments;
}
@Override
public void addEnchantment(Enchantment enchantment, int level) {
enchantments.put(enchantment, level);
}
@Override
public Map<Enchantment, Integer> getEnchantments() {
return enchantments;
}
@Override
public Object getHandle() {
return enchantments;
}
}

View File

@@ -0,0 +1,72 @@
package com.dfsek.terra.minestom.item;
import com.dfsek.terra.api.inventory.Item;
import com.dfsek.terra.api.inventory.item.Enchantment;
import com.dfsek.terra.api.inventory.item.ItemMeta;
import net.minestom.server.MinecraftServer;
import net.minestom.server.item.ItemComponent;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.component.EnchantmentList;
import net.minestom.server.registry.DynamicRegistry;
import net.minestom.server.registry.DynamicRegistry.Key;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Objects;
public class MinestomItemStack implements com.dfsek.terra.api.inventory.ItemStack {
private ItemStack base;
public MinestomItemStack(net.minestom.server.item.@NotNull ItemStack base) {
this.base = base;
}
@Override
public Object getHandle() {
return base;
}
@Override
public int getAmount() {
return base.amount();
}
@Override
public void setAmount(int i) {
base = base.withAmount(i);
}
@Override
public Item getType() {
return new MinestomMaterial(base.material());
}
@Override
public ItemMeta getItemMeta() {
HashMap<Enchantment, Integer> enchantments = new HashMap<>();
EnchantmentList enchantmentList = base.get(ItemComponent.ENCHANTMENTS);
if(enchantmentList != null) {
enchantmentList.enchantments().forEach((enchantmentKey, integer) -> {
enchantments.put(
new MinestomEnchantment(Objects.requireNonNull(MinecraftServer.getEnchantmentRegistry().get(enchantmentKey))), integer);
});
}
return new MinestomItemMeta(enchantments);
}
@Override
public void setItemMeta(ItemMeta meta) {
HashMap<Key<net.minestom.server.item.enchant.Enchantment>, Integer> enchantments = new HashMap<>();
DynamicRegistry<net.minestom.server.item.enchant.Enchantment> registry = MinecraftServer.getEnchantmentRegistry();
meta.getEnchantments().forEach((key, value) -> {
MinestomEnchantment enchantment = (MinestomEnchantment) key;
enchantments.put(registry.getKey(enchantment.getHandle()), value);
});
EnchantmentList list = new EnchantmentList(enchantments);
base = base.with(ItemComponent.ENCHANTMENTS, list);
}
}

View File

@@ -0,0 +1,34 @@
package com.dfsek.terra.minestom.item;
import com.dfsek.terra.api.inventory.Item;
import com.dfsek.terra.api.inventory.ItemStack;
import net.minestom.server.item.Material;
public class MinestomMaterial implements Item {
private final Material delegate;
public MinestomMaterial(Material delegate) {
this.delegate = delegate;
}
public MinestomMaterial(String id) {
this.delegate = Material.fromNamespaceId(id);
}
@Override
public ItemStack newItemStack(int amount) {
return new MinestomItemStack(net.minestom.server.item.ItemStack.builder(delegate).amount(amount).build());
}
@Override
public double getMaxDurability() {
return 0;
}
@Override
public Material getHandle() {
return delegate;
}
}

View File

@@ -0,0 +1,68 @@
package com.dfsek.terra.minestom.world;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator;
import com.dfsek.terra.api.world.chunk.generation.stage.GenerationStage;
import com.dfsek.terra.api.world.chunk.generation.util.GeneratorWrapper;
import com.dfsek.terra.minestom.chunk.CachedChunk;
import com.dfsek.terra.minestom.chunk.GeneratedChunkCache;
import net.minestom.server.coordinate.Point;
import net.minestom.server.instance.generator.GenerationUnit;
import net.minestom.server.instance.generator.Generator;
import org.jetbrains.annotations.NotNull;
public class MinestomChunkGeneratorWrapper implements Generator, GeneratorWrapper {
private final GeneratedChunkCache cache;
private ChunkGenerator generator;
private final TerraMinestomWorld world;
private ConfigPack pack;
public MinestomChunkGeneratorWrapper(ChunkGenerator generator, TerraMinestomWorld world, ConfigPack pack) {
this.generator = generator;
this.world = world;
this.pack = pack;
this.cache = new GeneratedChunkCache(world.getDimensionType(), generator, world);
}
public ChunkGenerator getGenerator() {
return generator;
}
@Override
public void generate(@NotNull GenerationUnit unit) {
Point start = unit.absoluteStart();
int x = start.chunkX();
int z = start.chunkZ();
CachedChunk chunk = cache.at(x, z);
chunk.writeRelative(unit.modifier());
unit.fork(setter -> {
MinestomProtoWorld protoWorld = new MinestomProtoWorld(cache, x, z, world, setter);
for(GenerationStage stage : world.getPack().getStages()) {
stage.populate(protoWorld);
}
});
}
public ConfigPack getPack() {
return pack;
}
public void setPack(ConfigPack pack) {
this.pack = pack;
this.generator = pack.getGeneratorProvider().newInstance(pack);
}
public void displayStats() {
cache.displayStats();
}
@Override
public ChunkGenerator getHandle() {
return generator;
}
}

View File

@@ -0,0 +1,111 @@
package com.dfsek.terra.minestom.world;
import com.dfsek.terra.api.block.entity.BlockEntity;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.entity.Entity;
import com.dfsek.terra.api.entity.EntityType;
import com.dfsek.terra.api.world.ServerWorld;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator;
import com.dfsek.terra.api.world.chunk.generation.ProtoWorld;
import com.dfsek.terra.minestom.chunk.CachedChunk;
import com.dfsek.terra.minestom.chunk.GeneratedChunkCache;
import com.dfsek.terra.minestom.entity.DeferredMinestomEntity;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.Block.Setter;
public class MinestomProtoWorld implements ProtoWorld {
private final GeneratedChunkCache cache;
private final int x;
private final int z;
private final TerraMinestomWorld world;
private final Setter modifier;
public MinestomProtoWorld(GeneratedChunkCache cache, int x, int z, TerraMinestomWorld world, Setter modifier) {
this.cache = cache;
this.x = x;
this.z = z;
this.world = world;
this.modifier = modifier;
}
@Override
public int centerChunkX() {
return x;
}
@Override
public int centerChunkZ() {
return z;
}
@Override
public ServerWorld getWorld() {
return world;
}
@Override
public void setBlockState(int x, int y, int z, BlockState data, boolean physics) {
modifier.setBlock(x, y, z, (Block) data.getHandle());
}
@Override
public Entity spawnEntity(double x, double y, double z, EntityType entityType) {
TerraMinestomWorld world = this.world;
DeferredMinestomEntity entity = new DeferredMinestomEntity(x, y, z, entityType, world);
world.enqueue(entity.pos(), (chunk) -> entity.spawn());
return entity;
}
@Override
public BlockState getBlockState(int x, int y, int z) {
int chunkX = x >> 4;
int chunkZ = z >> 4;
CachedChunk chunk = cache.at(chunkX, chunkZ);
return chunk.getBlock(x & 15, y, z & 15);
}
@Override
public BlockEntity getBlockEntity(int x, int y, int z) {
return world.getBlockEntity(x, y, z);
}
@Override
public ChunkGenerator getGenerator() {
return world.getGenerator();
}
@Override
public BiomeProvider getBiomeProvider() {
return world.getBiomeProvider();
}
@Override
public ConfigPack getPack() {
return world.getPack();
}
@Override
public long getSeed() {
return world.getSeed();
}
@Override
public int getMaxHeight() {
return world.getMaxHeight();
}
@Override
public int getMinHeight() {
return world.getMinHeight();
}
@Override
public Object getHandle() {
return world;
}
}

View File

@@ -0,0 +1,31 @@
package com.dfsek.terra.minestom.world;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.entity.EntityType;
import com.dfsek.terra.api.handle.WorldHandle;
import com.dfsek.terra.minestom.block.MinestomBlockState;
import com.dfsek.terra.minestom.entity.MinestomEntityType;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.NotNull;
public class MinestomWorldHandle implements WorldHandle {
private static final MinestomBlockState AIR = new MinestomBlockState(Block.AIR);
@Override
public @NotNull BlockState createBlockState(@NotNull String data) {
return new MinestomBlockState(data);
}
@Override
public @NotNull BlockState air() {
return AIR;
}
@Override
public @NotNull EntityType getEntity(@NotNull String id) {
return new MinestomEntityType(id);
}
}

View File

@@ -0,0 +1,130 @@
package com.dfsek.terra.minestom.world;
import com.dfsek.terra.api.block.entity.BlockEntity;
import com.dfsek.terra.api.block.state.BlockState;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.entity.Entity;
import com.dfsek.terra.api.entity.EntityType;
import com.dfsek.terra.api.world.ServerWorld;
import com.dfsek.terra.api.world.biome.generation.BiomeProvider;
import com.dfsek.terra.api.world.chunk.Chunk;
import com.dfsek.terra.api.world.chunk.generation.ChunkGenerator;
import com.dfsek.terra.api.world.info.WorldProperties;
import com.dfsek.terra.minestom.api.BlockEntityFactory;
import com.dfsek.terra.minestom.api.EntityFactory;
import com.dfsek.terra.minestom.block.MinestomBlockState;
import com.dfsek.terra.minestom.entity.MinestomEntity;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.BlockVec;
import net.minestom.server.coordinate.Point;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import net.minestom.server.world.DimensionType;
import java.util.function.Consumer;
public final class TerraMinestomWorld implements ServerWorld, WorldProperties {
private final Instance instance;
private final ConfigPack pack;
private final long seed;
private final DimensionType dimensionType;
private final MinestomChunkGeneratorWrapper wrapper;
private final EntityFactory entityFactory;
private final BlockEntityFactory blockEntityFactory;
public TerraMinestomWorld(Instance instance, ConfigPack pack, long seed, EntityFactory entityFactory,
BlockEntityFactory blockEntityFactory) {
this.instance = instance;
this.pack = pack;
this.seed = seed;
this.dimensionType = MinecraftServer.getDimensionTypeRegistry().get(instance.getDimensionType());
this.blockEntityFactory = blockEntityFactory;
this.wrapper = new MinestomChunkGeneratorWrapper(pack.getGeneratorProvider().newInstance(pack), this, pack);
this.entityFactory = entityFactory;
instance.setGenerator(this.wrapper);
}
@Override
public Chunk getChunkAt(int x, int z) {
return null;
}
@Override
public void setBlockState(int x, int y, int z, BlockState data, boolean physics) {
instance.setBlock(x, y, z, (Block) data.getHandle());
}
@Override
public Entity spawnEntity(double x, double y, double z, EntityType entityType) {
return MinestomEntity.spawn(x, y, z, entityType, this);
}
public void displayStats() {
wrapper.displayStats();
}
@Override
public BlockState getBlockState(int x, int y, int z) {
return new MinestomBlockState(instance.getBlock(x, y, z));
}
@Override
public BlockEntity getBlockEntity(int x, int y, int z) {
return blockEntityFactory.createBlockEntity(new BlockVec(x, y, z));
}
@Override
public ChunkGenerator getGenerator() {
return wrapper.getGenerator();
}
@Override
public BiomeProvider getBiomeProvider() {
return pack.getBiomeProvider();
}
@Override
public ConfigPack getPack() {
return pack;
}
@Override
public long getSeed() {
return seed;
}
@Override
public int getMaxHeight() {
return dimensionType.maxY();
}
@Override
public int getMinHeight() {
return dimensionType.minY();
}
@Override
public Instance getHandle() {
return instance;
}
public DimensionType getDimensionType() {
return dimensionType;
}
public EntityFactory getEntityFactory() {
return entityFactory;
}
public void enqueue(Point position, Consumer<net.minestom.server.instance.Chunk> action) {
instance.loadChunk(position.chunkX(), position.chunkZ()).thenAccept(action);
}
}

View File

@@ -0,0 +1,75 @@
package com.dfsek.terra.minestom.world;
import com.dfsek.terra.api.config.ConfigPack;
import com.dfsek.terra.api.registry.CheckedRegistry;
import com.dfsek.terra.minestom.MinestomPlatform;
import com.dfsek.terra.minestom.api.BlockEntityFactory;
import com.dfsek.terra.minestom.api.EntityFactory;
import com.dfsek.terra.minestom.block.DefaultBlockEntityFactory;
import com.dfsek.terra.minestom.entity.DefaultEntityFactory;
import net.minestom.server.MinecraftServer;
import net.minestom.server.instance.Instance;
import java.util.Random;
import java.util.function.Function;
public class TerraMinestomWorldBuilder {
private final Instance instance;
private ConfigPack pack;
private long seed = new Random().nextLong();
private EntityFactory entityFactory = new DefaultEntityFactory();
private BlockEntityFactory blockEntityFactory = new DefaultBlockEntityFactory();
private TerraMinestomWorldBuilder(Instance instance) { this.instance = instance; }
public static TerraMinestomWorldBuilder from(Instance instance) {
return new TerraMinestomWorldBuilder(instance);
}
public static TerraMinestomWorldBuilder builder() {
return new TerraMinestomWorldBuilder(MinecraftServer.getInstanceManager().createInstanceContainer());
}
public TerraMinestomWorldBuilder pack(ConfigPack pack) {
this.pack = pack;
return this;
}
public TerraMinestomWorldBuilder packById(String id) {
this.pack = MinestomPlatform.getInstance().getConfigRegistry().getByID(id).orElseThrow();
return this;
}
public TerraMinestomWorldBuilder findPack(Function<CheckedRegistry<ConfigPack>, ConfigPack> fn) {
this.pack = fn.apply(MinestomPlatform.getInstance().getConfigRegistry());
return this;
}
public TerraMinestomWorldBuilder defaultPack() {
return this.packById("OVERWORLD");
}
public TerraMinestomWorldBuilder seed(long seed) {
this.seed = seed;
return this;
}
public TerraMinestomWorldBuilder entityFactory(EntityFactory factory) {
this.entityFactory = factory;
return this;
}
public TerraMinestomWorldBuilder blockEntityFactory(BlockEntityFactory factory) {
this.blockEntityFactory = factory;
return this;
}
public TerraMinestomWorld attach() {
return new TerraMinestomWorld(instance, pack, seed, entityFactory, blockEntityFactory);
}
}