mirror of
https://github.com/GTNewHorizons/Botania.git
synced 2026-10-25 20:22:33 +00:00
Add Botania Multiblocks to StructureLib (#93)
This commit is contained in:
1 parent
f4f7485a12
commit
825758d639
8 files changed
+381
-11
No files matched your search
@@ -12,5 +12,7 @@ dependencies {
|
||||
compileOnly('curse.maven:cofh-lib-220333:2388748')
|
||||
compileOnly('curse.maven:minefactory-reloaded-66672:2366150')
|
||||
compileOnly('thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev')
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:StructureLib:1.4.18:dev")
|
||||
}
|
||||
|
||||
@@ -15,10 +15,15 @@ import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import cpw.mods.fml.common.FMLLog;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.ChunkCoordinates;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import vazkii.botania.api.lexicon.multiblock.compat.MultiblockCompatRegistry;
|
||||
import vazkii.botania.api.lexicon.multiblock.component.MultiblockComponent;
|
||||
import vazkii.botania.common.Botania;
|
||||
|
||||
/**
|
||||
* This class describes a Mutiblock object. It's used to display a
|
||||
@@ -39,10 +44,14 @@ public class Multiblock {
|
||||
* coords should be pivoted to the center of the structure.
|
||||
*/
|
||||
public void addComponent(MultiblockComponent component) {
|
||||
if(getComponentForLocation(component.relPos.posX, component.relPos.posY, component.relPos.posZ) != null)
|
||||
throw new IllegalArgumentException("Location in multiblock already occupied");
|
||||
ChunkCoordinates pos = component.relPos;
|
||||
if(getComponentForLocation(pos.posX, pos.posY, pos.posZ) != null) {
|
||||
MultiblockComponent comp = getComponentForLocation(pos.posX, pos.posY, pos.posZ);
|
||||
throw new IllegalArgumentException(
|
||||
"Location in multiblock {x=" + pos.posX + " y=" + pos.posY + " z=" + pos.posZ + "} already occupied by block " + comp.getBlock().getLocalizedName());
|
||||
}
|
||||
components.add(component);
|
||||
changeAxisForNewComponent(component.relPos.posX, component.relPos.posY, component.relPos.posZ);
|
||||
changeAxisForNewComponent(pos.posX, pos.posY, pos.posZ);
|
||||
calculateCostForNewComponent(component);
|
||||
addComponentToLocationCache(component);
|
||||
}
|
||||
@@ -138,6 +147,28 @@ public class Multiblock {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
public <T extends TileEntity> MultiblockSet makeSetRegisterStructure(
|
||||
Class<T> controllerTileClass, Block controllerBlock, MultiblockComponent... extra
|
||||
) {
|
||||
registerStructure(controllerTileClass, controllerBlock, extra);
|
||||
return makeSet();
|
||||
}
|
||||
|
||||
public <T extends TileEntity> void registerStructure(
|
||||
Class<T> controllerTileClass, Block controllerBlock, MultiblockComponent... extra
|
||||
) {
|
||||
if (Botania.structureLibLoaded) {
|
||||
try {
|
||||
MultiblockCompatRegistry.registerMultiblock(
|
||||
this, controllerTileClass, controllerBlock, extra
|
||||
);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
FMLLog.log(Level.ERROR, "Failed to load a Botania Multiblock into StructureLib.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a MultiblockSet from this Multiblock and its rotations using
|
||||
* createRotations().
|
||||
@@ -176,7 +207,7 @@ public class Multiblock {
|
||||
pos.posX,
|
||||
pos.posY,
|
||||
pos.posZ
|
||||
), comp);
|
||||
), comp);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
package vazkii.botania.api.lexicon.multiblock.compat;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.IMultiblockInfoContainer;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
|
||||
import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment;
|
||||
import com.gtnewhorizon.structurelib.structure.StructureDefinition;
|
||||
import com.gtnewhorizon.structurelib.structure.StructureUtility;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.ChunkCoordinates;
|
||||
import vazkii.botania.api.lexicon.multiblock.Multiblock;
|
||||
import vazkii.botania.api.lexicon.multiblock.component.MultiblockComponent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* Only gets loaded if StructureLib is present inside the Modpack
|
||||
*/
|
||||
public class MultiblockCompatRegistry {
|
||||
|
||||
public static <T extends TileEntity> void registerMultiblock(
|
||||
Multiblock mb, Class<T> controllerTileClass,
|
||||
Block controllerBlock,
|
||||
MultiblockComponent... extra
|
||||
) {
|
||||
List<MultiblockComponent> components = mb.getComponents();
|
||||
if (extra.length != 0) {
|
||||
components = new ArrayList<>(components);
|
||||
Collections.addAll(components, extra);
|
||||
}
|
||||
|
||||
Map<Integer, RawStructureData> structureDataMap = new HashMap<>();
|
||||
int min_x = Integer.MAX_VALUE, max_x = Integer.MIN_VALUE;
|
||||
int min_y = Integer.MAX_VALUE, max_y = Integer.MIN_VALUE;
|
||||
int min_z = Integer.MAX_VALUE, max_z = Integer.MIN_VALUE;
|
||||
|
||||
char ch = 'a';
|
||||
for (MultiblockComponent component : components) {
|
||||
int hash = getHash(component.getBlock(), component.getMeta());
|
||||
ChunkCoordinates pos = component.getRelativePosition();
|
||||
RawStructureData structureData = structureDataMap.get(hash);
|
||||
if (structureData == null) {
|
||||
List<ChunkCoordinates> coordinates = new ArrayList<>();
|
||||
structureData = new RawStructureData(
|
||||
coordinates,
|
||||
component.getBlock(),
|
||||
component.getMeta(),
|
||||
ch++
|
||||
);
|
||||
structureDataMap.put(hash, structureData);
|
||||
}
|
||||
structureData.getPositions().add(pos);
|
||||
|
||||
min_x = Math.min(min_x, pos.posX); max_x = Math.max(max_x, pos.posX);
|
||||
min_y = Math.min(min_y, pos.posY); max_y = Math.max(max_y, pos.posY);
|
||||
min_z = Math.min(min_z, pos.posZ); max_z = Math.max(max_z, pos.posZ);
|
||||
}
|
||||
|
||||
int x_size = max_x - min_x + 1;
|
||||
int y_size = max_y - min_y + 1;
|
||||
int z_size = max_z - min_z + 1;
|
||||
|
||||
//Data is saved in [z][y][x] format
|
||||
String[][] stringData = new String[z_size][y_size];
|
||||
|
||||
char[] buffer = new char[x_size];
|
||||
Arrays.fill(buffer, ' ');
|
||||
//Since Strings and their content are immutable, this is fine.
|
||||
String filledWithSpaces = new String(buffer);
|
||||
for (int z = 0; z < z_size; z++) {
|
||||
for (int y = 0; y < y_size; y++) {
|
||||
stringData[z][y] = filledWithSpaces;
|
||||
}
|
||||
}
|
||||
|
||||
int controller_x = 0, controller_y = 0, controller_z = 0;
|
||||
StringBuilder builder = new StringBuilder(x_size);
|
||||
for (RawStructureData data : structureDataMap.values()) {
|
||||
for (ChunkCoordinates pos : data.getPositions()) {
|
||||
int x = pos.posX - min_x;
|
||||
int z = pos.posZ - min_z;
|
||||
//Data has to get flipped
|
||||
int y = (stringData[z].length - 1) - (pos.posY - min_y);
|
||||
|
||||
if (data.getBlock() == controllerBlock) {
|
||||
controller_x = x;
|
||||
controller_y = y;
|
||||
controller_z = z;
|
||||
}
|
||||
|
||||
String s = stringData[z][y];
|
||||
builder.append(s);
|
||||
builder.setCharAt(x, data.getBlockIdentifier());
|
||||
stringData[z][y] = builder.toString();
|
||||
builder.setLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
StructureDefinition.Builder<? super T> structureBuilder = IStructureDefinition.builder()
|
||||
.addShape("main", stringData);
|
||||
|
||||
for (RawStructureData data : structureDataMap.values()) {
|
||||
structureBuilder.addElement(
|
||||
data.getBlockIdentifier(),
|
||||
StructureUtility.ofBlock(
|
||||
data.getBlock(),
|
||||
data.getMetadata()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
IMultiblockInfoContainer.registerTileClass(
|
||||
controllerTileClass,
|
||||
new BotaniaMultiblockInfoContainer<>(
|
||||
structureBuilder.build(),
|
||||
controller_x,
|
||||
controller_y,
|
||||
controller_z
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private static int getHash(Block block, int metadata) {
|
||||
return block.hashCode() * 31 + metadata;
|
||||
}
|
||||
|
||||
|
||||
private static class BotaniaMultiblockInfoContainer<T extends TileEntity> implements IMultiblockInfoContainer<T> {
|
||||
|
||||
private final IStructureDefinition<? super T> structure;
|
||||
private final int x_offset, y_offset, z_offset;
|
||||
|
||||
public BotaniaMultiblockInfoContainer(
|
||||
IStructureDefinition<? super T> structure,
|
||||
int x_offset, int y_offset, int z_offset
|
||||
) {
|
||||
this.structure = structure;
|
||||
this.x_offset = x_offset;
|
||||
this.y_offset = y_offset;
|
||||
this.z_offset = z_offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void construct(ItemStack stackSize, boolean hintsOnly, T tileEntity, ExtendedFacing aSide) {
|
||||
structure.buildOrHints(
|
||||
tileEntity,
|
||||
stackSize,
|
||||
"main",
|
||||
tileEntity.getWorldObj(),
|
||||
noSideWay(aSide),
|
||||
tileEntity.xCoord,
|
||||
tileEntity.yCoord,
|
||||
tileEntity.zCoord,
|
||||
x_offset,
|
||||
y_offset,
|
||||
z_offset,
|
||||
hintsOnly
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int survivalConstruct(ItemStack stackSize, int elementBudge, ISurvivalBuildEnvironment env, T tileEntity, ExtendedFacing aSide) {
|
||||
return structure.survivalBuild(
|
||||
tileEntity,
|
||||
stackSize,
|
||||
"main",
|
||||
tileEntity.getWorldObj(),
|
||||
noSideWay(aSide),
|
||||
tileEntity.xCoord,
|
||||
tileEntity.yCoord,
|
||||
tileEntity.zCoord,
|
||||
x_offset,
|
||||
y_offset,
|
||||
z_offset,
|
||||
elementBudge,
|
||||
env,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDescription(ItemStack stackSize) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
private static ExtendedFacing noSideWay(ExtendedFacing aSide) {
|
||||
return aSide.getDirection().offsetY != 0 ? ExtendedFacing.DEFAULT : aSide.getOppositeDirection();
|
||||
}
|
||||
}
|
||||
|
||||
private static class RawStructureData {
|
||||
private final List<ChunkCoordinates> positions;
|
||||
private final char identifier;
|
||||
private final Block block;
|
||||
private final int metadata;
|
||||
|
||||
public RawStructureData(List<ChunkCoordinates> positions, Block block, int metadata, char identifier) {
|
||||
this.positions = positions;
|
||||
this.identifier = identifier;
|
||||
this.block = block;
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public Block getBlock() {
|
||||
return block;
|
||||
}
|
||||
|
||||
public int getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public List<ChunkCoordinates> getPositions() {
|
||||
return positions;
|
||||
}
|
||||
|
||||
public char getBlockIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ public class Botania {
|
||||
public static boolean coloredLightsLoaded = false;
|
||||
public static boolean etFuturumLoaded = false;
|
||||
public static boolean storageDrawersLoaded = false;
|
||||
public static boolean structureLibLoaded = false;
|
||||
|
||||
public static ILightHelper lightHelper;
|
||||
|
||||
@@ -61,6 +62,7 @@ public class Botania {
|
||||
coloredLightsLoaded = Loader.isModLoaded("easycoloredlights");
|
||||
etFuturumLoaded = Loader.isModLoaded("etfuturum");
|
||||
storageDrawersLoaded = Loader.isModLoaded("StorageDrawers");
|
||||
structureLibLoaded = Loader.isModLoaded("structurelib");
|
||||
|
||||
lightHelper = coloredLightsLoaded ? new LightHelperColored() : new LightHelperVanilla();
|
||||
proxy.preInit(event);
|
||||
|
||||
@@ -116,7 +116,7 @@ public class TileAlfPortal extends TileMod {
|
||||
mb.addComponent(0, 1, 0, ModBlocks.alfPortal, 0);
|
||||
mb.setRenderOffset(0, -1, 0);
|
||||
|
||||
return mb.makeSet();
|
||||
return mb.makeSetRegisterStructure(TileAlfPortal.class, ModBlocks.alfPortal);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,8 +13,13 @@ package vazkii.botania.common.block.tile;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cpw.mods.fml.common.registry.GameRegistry;
|
||||
import net.minecraft.block.Block;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.texture.IIconRegister;
|
||||
import net.minecraft.creativetab.CreativeTabs;
|
||||
import net.minecraft.enchantment.Enchantment;
|
||||
import net.minecraft.enchantment.EnchantmentHelper;
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -22,25 +27,30 @@ import net.minecraft.entity.item.EntityItem;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.init.Items;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.AxisAlignedBB;
|
||||
import net.minecraft.util.ChunkCoordinates;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraft.world.World;
|
||||
import vazkii.botania.api.BotaniaAPI;
|
||||
import vazkii.botania.api.internal.VanillaPacketDispatcher;
|
||||
import vazkii.botania.api.lexicon.multiblock.Multiblock;
|
||||
import vazkii.botania.api.lexicon.multiblock.MultiblockSet;
|
||||
import vazkii.botania.api.lexicon.multiblock.component.FlowerComponent;
|
||||
import vazkii.botania.api.lexicon.multiblock.component.MultiblockComponent;
|
||||
import vazkii.botania.api.mana.IManaPool;
|
||||
import vazkii.botania.api.mana.spark.ISparkAttachable;
|
||||
import vazkii.botania.api.mana.spark.ISparkEntity;
|
||||
import vazkii.botania.api.mana.spark.SparkHelper;
|
||||
import vazkii.botania.client.core.helper.RenderHelper;
|
||||
import vazkii.botania.common.Botania;
|
||||
import vazkii.botania.common.block.BlockModFlower;
|
||||
import vazkii.botania.common.block.ModBlocks;
|
||||
import vazkii.botania.common.item.block.ItemBlockMod;
|
||||
|
||||
public class TileEnchanter extends TileMod implements ISparkAttachable {
|
||||
|
||||
@@ -81,20 +91,68 @@ public class TileEnchanter extends TileMod implements ISparkAttachable {
|
||||
{ -1, 0, -1 }, { 1, 0, -1 }, { -1, 0, 1 }, { 1, 0, 1 }
|
||||
};
|
||||
|
||||
//Note: It currently doesn't show up when looking at a Lapis Block in NEI and I have no idea how to fix it.
|
||||
public static MultiblockSet makeMultiblockSet() {
|
||||
Multiblock mb = new Multiblock();
|
||||
|
||||
for(int[] o : OBSIDIAN_LOCATIONS)
|
||||
mb.addComponent(o[0], o[1] + 1, o[2], Blocks.obsidian, 0);
|
||||
|
||||
//Flowers don't seem to properly work in StructureLib (since they need a block below them)
|
||||
//This solution also allows the flowers to cycle through each other
|
||||
//Not sure if there's a better solution for this, but hey it works
|
||||
Block physicsDefyingFlower = new StructureLibFlower();
|
||||
|
||||
List<MultiblockComponent> extraDisplayList = new ArrayList<>();
|
||||
|
||||
for(int[] p : PYLON_LOCATIONS[0]) {
|
||||
mb.addComponent(p[0], p[1] + 1, p[2], ModBlocks.pylon, 0);
|
||||
mb.addComponent(new FlowerComponent(new ChunkCoordinates(p[0], p[1], p[2]), ModBlocks.flower));
|
||||
|
||||
extraDisplayList.add(new MultiblockComponent(
|
||||
new ChunkCoordinates(p[0], p[1], p[2]),
|
||||
physicsDefyingFlower,
|
||||
0
|
||||
));
|
||||
extraDisplayList.add(new MultiblockComponent(
|
||||
new ChunkCoordinates(p[0], p[1] - 1, p[2]),
|
||||
Blocks.grass,
|
||||
0
|
||||
));
|
||||
}
|
||||
for(int[] f : FLOWER_LOCATIONS) {
|
||||
extraDisplayList.add(new MultiblockComponent(
|
||||
new ChunkCoordinates(f[0], f[1] + 1, f[2]),
|
||||
physicsDefyingFlower,
|
||||
0
|
||||
));
|
||||
extraDisplayList.add(new MultiblockComponent(
|
||||
new ChunkCoordinates(f[0], f[1], f[2]),
|
||||
Blocks.grass,
|
||||
0
|
||||
));
|
||||
}
|
||||
for(int[] f : FLOWER_LOCATIONS)
|
||||
mb.addComponent(new FlowerComponent(new ChunkCoordinates(f[0], f[1] + 1, f[2]), ModBlocks.flower));
|
||||
|
||||
mb.addComponent(0, 1, 0, Blocks.lapis_block, 0);
|
||||
|
||||
mb.addComponent(0, 1, 0, ModBlocks.enchanter, 0);
|
||||
mb.registerStructure(
|
||||
TileEnchanter.class,
|
||||
ModBlocks.enchanter,
|
||||
extraDisplayList.toArray(new MultiblockComponent[0])
|
||||
);
|
||||
|
||||
//Add the actual flowers
|
||||
for(int[] p : PYLON_LOCATIONS[0]) {
|
||||
mb.addComponent(new FlowerComponent(
|
||||
new ChunkCoordinates(p[0], p[1], p[2]),
|
||||
ModBlocks.flower
|
||||
));
|
||||
}
|
||||
for(int[] f : FLOWER_LOCATIONS) {
|
||||
mb.addComponent(new FlowerComponent(
|
||||
new ChunkCoordinates(f[0], f[1] + 1, f[2]),
|
||||
ModBlocks.flower
|
||||
));
|
||||
}
|
||||
return mb.makeSet();
|
||||
}
|
||||
|
||||
@@ -128,7 +186,7 @@ public class TileEnchanter extends TileMod implements ISparkAttachable {
|
||||
if(getBlockMetadata() < PYLON_LOCATIONS.length)
|
||||
for(int[] pylon : PYLON_LOCATIONS[getBlockMetadata()]) {
|
||||
TileEntity tile = worldObj.getTileEntity(xCoord + pylon[0], yCoord + pylon[1], zCoord + pylon[2]);
|
||||
if(tile != null && tile instanceof TilePylon)
|
||||
if(tile instanceof TilePylon)
|
||||
((TilePylon) tile).activated = false;
|
||||
}
|
||||
|
||||
@@ -429,4 +487,52 @@ public class TileEnchanter extends TileMod implements ISparkAttachable {
|
||||
}
|
||||
}
|
||||
|
||||
private static class StructureLibFlower extends BlockModFlower {
|
||||
|
||||
public StructureLibFlower() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Block setBlockName(String par1Str) {
|
||||
GameRegistry.registerBlock(this, ItemBlockMod.class, "flower_structurelib");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIcon getIcon(int par1, int par2) {
|
||||
return super.getIcon(par1, (int) (BotaniaAPI.internalHandler.getWorldElapsedTicks() / 20) % 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUnlocalizedName() {
|
||||
return "tile.flower_structurelib";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerBlockIcons(IIconRegister par1IconRegister) {
|
||||
//has to be empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getSubBlocks(Item par1, CreativeTabs par2CreativeTabs, List par3List) {
|
||||
//has to be empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPlaceBlockAt(World worldIn, int x, int y, int z) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBlockStay(World worldIn, int x, int y, int z) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canPlaceBlockOn(Block ground) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -57,7 +57,7 @@ public class TileTerraPlate extends TileMod implements ISparkAttachable {
|
||||
mb.addComponent(0, 1, 0, ModBlocks.terraPlate, 0);
|
||||
mb.setRenderOffset(0, 1, 0);
|
||||
|
||||
return mb.makeSet();
|
||||
return mb.makeSetRegisterStructure(TileTerraPlate.class, ModBlocks.terraPlate);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -650,6 +650,7 @@ tile.botania:flower12.name=Mystical Brown Flower
|
||||
tile.botania:flower13.name=Mystical Green Flower
|
||||
tile.botania:flower14.name=Mystical Red Flower
|
||||
tile.botania:flower15.name=Mystical Black Flower
|
||||
tile.botania:flower_structurelib.name=Any Botania Flower
|
||||
tile.botania:altar0.name=Petal Apothecary
|
||||
tile.botania:altar1.name=Forest Petal Apothecary
|
||||
tile.botania:altar2.name=Plains Petal Apothecary
|
||||
|
||||
Reference in new issue
Block a user