Experimental prefab system & structure generation

This commit is contained in:
kuroppoi 2021-05-30 23:04:01 +02:00
parent b11984cb03
commit 3520fe2a44
65 changed files with 1865 additions and 66 deletions

View file

@ -10,6 +10,7 @@ import brainwine.gameserver.command.CommandManager;
import brainwine.gameserver.entity.player.PlayerManager;
import brainwine.gameserver.loot.LootManager;
import brainwine.gameserver.msgpack.MessagePackHelper;
import brainwine.gameserver.prefab.PrefabManager;
import brainwine.gameserver.server.NetworkRegistry;
import brainwine.gameserver.server.Server;
import brainwine.gameserver.zone.ZoneManager;
@ -23,6 +24,7 @@ public class GameServer {
private final Thread handlerThread;
private final Queue<Runnable> tasks = new ConcurrentLinkedQueue<>();
private final LootManager lootManager;
private final PrefabManager prefabManager;
private final ZoneManager zoneManager;
private final PlayerManager playerManager;
private final Server server;
@ -38,6 +40,7 @@ public class GameServer {
GameConfiguration.init();
MessagePackHelper.init();
lootManager = new LootManager();
prefabManager = new PrefabManager();
StaticZoneGenerator.init();
zoneManager = new ZoneManager();
playerManager = new PlayerManager();
@ -106,6 +109,10 @@ public class GameServer {
return lootManager;
}
public PrefabManager getPrefabManager() {
return prefabManager;
}
public ZoneManager getZoneManager() {
return zoneManager;
}

View file

@ -13,9 +13,11 @@ import org.apache.logging.log4j.Logger;
import brainwine.gameserver.command.commands.AdminCommand;
import brainwine.gameserver.command.commands.BroadcastCommand;
import brainwine.gameserver.command.commands.ExportCommand;
import brainwine.gameserver.command.commands.GenerateZoneCommand;
import brainwine.gameserver.command.commands.GiveCommand;
import brainwine.gameserver.command.commands.HelpCommand;
import brainwine.gameserver.command.commands.ImportCommand;
import brainwine.gameserver.command.commands.KickCommand;
import brainwine.gameserver.command.commands.PlayerIdCommand;
import brainwine.gameserver.command.commands.PositionCommand;
@ -62,6 +64,8 @@ public class CommandManager {
registerCommand(new GiveCommand());
registerCommand(new GenerateZoneCommand());
registerCommand(new SeedCommand());
registerCommand(new ExportCommand());
registerCommand(new ImportCommand());
registerCommand(new PositionCommand());
}

View file

@ -0,0 +1,97 @@
package brainwine.gameserver.command.commands;
import static brainwine.gameserver.entity.player.NotificationType.ALERT;
import java.util.Arrays;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.prefab.Prefab;
import brainwine.gameserver.prefab.PrefabManager;
import brainwine.gameserver.zone.Zone;
public class ExportCommand extends Command {
public static final int SIZE_LIMIT = 10000;
@Override
public void execute(CommandExecutor executor, String[] args) {
if(args.length < 5) {
executor.notify(String.format("Usage: %s", getUsage(executor)), ALERT);
return;
}
Zone zone = ((Player)executor).getZone();
PrefabManager prefabManager = GameServer.getInstance().getPrefabManager();
String name = String.join(" ", Arrays.copyOfRange(args, 4, args.length));
if(prefabManager.getPrefab(name) != null) {
executor.notify("A prefab with that name already exists.", ALERT);
return;
}
int x = 0;
int y = 0;
int width = 0;
int height = 0;
try {
x = Integer.parseInt(args[0]);
y = Integer.parseInt(args[1]);
width = Integer.parseInt(args[2]);
height = Integer.parseInt(args[3]);
} catch(NumberFormatException e) {
executor.notify("Parameters must be valid numbers.", ALERT);
return;
}
if(width < 0 || height < 0) {
executor.notify("Width and height must be positive.", ALERT);
return;
} else if(width * height > SIZE_LIMIT) {
executor.notify(String.format("Sorry, your prefab is too large. Max size: %s blocks.", SIZE_LIMIT), ALERT);
return;
} else if(x < 0 || x + width >= zone.getWidth() || y < 0 || y + height >= zone.getHeight()) {
executor.notify("These coordinates are out of bounds.", ALERT);
return;
}
Prefab prefab = zone.chop(x, y, width, height);
if(prefab == null) {
executor.notify("Sorry, something went wrong. Please try again.", ALERT);
return;
}
executor.notify("Exporting your prefab ...", ALERT);
try {
prefabManager.registerPrefab(name, prefab);
executor.notify(String.format("Your prefab '%s' was successfully exported!", name), ALERT);
} catch (Exception e) {
executor.notify(String.format("An error occured while exporting prefab '%s': %s", name, e.getMessage()), ALERT);
}
}
@Override
public String getName() {
return "export";
}
@Override
public String getDescription() {
return "Exports a section of a zone to a prefab file.";
}
@Override
public String getUsage(CommandExecutor executor) {
return "/export <x> <y> <width> <height> <name>";
}
@Override
public boolean canExecute(CommandExecutor executor) {
return executor instanceof Player && executor.isAdmin();
}
}

View file

@ -0,0 +1,60 @@
package brainwine.gameserver.command.commands;
import static brainwine.gameserver.entity.player.NotificationType.ALERT;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.command.Command;
import brainwine.gameserver.command.CommandExecutor;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.prefab.Prefab;
public class ImportCommand extends Command {
@Override
public void execute(CommandExecutor executor, String[] args) {
if(args.length < 3) {
executor.notify(String.format("Usage: %s", getUsage(executor)), ALERT);
return;
}
int x = 0;
int y = 0;
try {
x = Integer.parseInt(args[1]);
y = Integer.parseInt(args[2]);
} catch(NumberFormatException e) {
executor.notify("X and Y must be valid numbers.", ALERT);
return;
}
Prefab prefab = GameServer.getInstance().getPrefabManager().getPrefab(args[0]);
if(prefab == null) {
executor.notify("Sorry, could not find a prefab with that name.", ALERT);
return;
}
((Player)executor).getZone().placePrefab(prefab, x, y);
}
@Override
public String getName() {
return "import";
}
@Override
public String getDescription() {
return "Places a prefab at the specified location.";
}
@Override
public String getUsage(CommandExecutor executor) {
return "/import <prefab> <x> <y>";
}
@Override
public boolean canExecute(CommandExecutor executor) {
return executor instanceof Player && executor.isAdmin();
}
}

View file

@ -11,6 +11,8 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import brainwine.gameserver.util.Vector2i;
@JsonIgnoreProperties(ignoreUnknown = true)
public class Item {
@ -36,6 +38,9 @@ public class Item {
@JsonProperty("title")
private String title;
@JsonProperty("rotation")
private String rotation;
@JsonProperty("loot_graphic")
private LootGraphic lootGraphic = LootGraphic.NONE;
@ -51,6 +56,9 @@ public class Item {
@JsonProperty("meta")
private MetaType meta = MetaType.NONE;
@JsonProperty("size")
private Vector2i size = new Vector2i(1, 1);
@JsonProperty("field")
private int field;
@ -130,6 +138,10 @@ public class Item {
return title;
}
public boolean isMirrorable() {
return rotation != null && rotation.equalsIgnoreCase("mirror");
}
public boolean isAir() {
return id == 0;
}
@ -166,6 +178,14 @@ public class Item {
return meta;
}
public int getBlockWidth() {
return size.getX();
}
public int getBlockHeight() {
return size.getY();
}
public boolean isDish() {
return field > 1;
}

View file

@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
public enum ModType {
DECAY,
ROTATION,
@JsonEnumDefaultValue
NONE;

View file

@ -12,17 +12,19 @@ import org.msgpack.packer.BufferPacker;
import org.msgpack.unpacker.BufferUnpacker;
import brainwine.gameserver.item.Item;
import brainwine.gameserver.msgpack.models.AppearanceData;
import brainwine.gameserver.msgpack.models.BlockUseData;
import brainwine.gameserver.msgpack.models.DialogInputData;
import brainwine.gameserver.msgpack.models.AppearanceData;
import brainwine.gameserver.msgpack.templates.AppearanceDataTemplate;
import brainwine.gameserver.msgpack.templates.BlockArrayTemplate;
import brainwine.gameserver.msgpack.templates.BlockTemplate;
import brainwine.gameserver.msgpack.templates.BlockUseDataTemplate;
import brainwine.gameserver.msgpack.templates.ChunkTemplate;
import brainwine.gameserver.msgpack.templates.DialogInputDataTemplate;
import brainwine.gameserver.msgpack.templates.EnumTemplate;
import brainwine.gameserver.msgpack.templates.ItemTemplate;
import brainwine.gameserver.msgpack.templates.AppearanceDataTemplate;
import brainwine.gameserver.reflections.ReflectionsHelper;
import brainwine.gameserver.msgpack.templates.PrefabTemplate;
import brainwine.gameserver.prefab.Prefab;
import brainwine.gameserver.util.ReflectionsHelper;
import brainwine.gameserver.util.ZipUtils;
import brainwine.gameserver.zone.Block;
@ -44,7 +46,9 @@ public class MessagePackHelper {
logger.info("Registering MessagePack templates ...");
messagePack.register(Item.class, new ItemTemplate());
messagePack.register(Block.class, new BlockTemplate());
messagePack.register(Block[].class, new BlockArrayTemplate());
messagePack.register(Chunk.class, new ChunkTemplate());
messagePack.register(Prefab.class, new PrefabTemplate());
messagePack.register(BlockUseData.class, new BlockUseDataTemplate());
messagePack.register(DialogInputData.class, new DialogInputDataTemplate());
messagePack.register(AppearanceData.class, new AppearanceDataTemplate());

View file

@ -0,0 +1,49 @@
package brainwine.gameserver.msgpack.templates;
import java.io.IOException;
import org.msgpack.MessageTypeException;
import org.msgpack.packer.Packer;
import org.msgpack.template.AbstractTemplate;
import org.msgpack.unpacker.Unpacker;
import brainwine.gameserver.zone.Block;
public class BlockArrayTemplate extends AbstractTemplate<Block[]> {
@Override
public void write(Packer packer, Block[] blocks, boolean required) throws IOException {
if(blocks == null) {
if(required) {
throw new MessageTypeException("Attempted to write null");
}
packer.writeNil();
return;
}
packer.writeArrayBegin(blocks.length * 3);
for(Block block : blocks) {
packer.write(block);
}
packer.writeArrayEnd();
}
@Override
public Block[] read(Unpacker unpacker, Block[] to, boolean required) throws IOException {
if(!required && unpacker.trySkipNil()) {
return null;
}
Block[] blocks = new Block[unpacker.readArrayBegin() / 3];
for(int i = 0; i < blocks.length; i++) {
blocks[i] = unpacker.read(Block.class);
}
unpacker.readArrayEnd();
return blocks;
}
}

View file

@ -29,13 +29,7 @@ public class ChunkTemplate extends AbstractTemplate<Chunk> {
packer.write(chunk.getY());
packer.write(chunk.getWidth());
packer.write(chunk.getHeight());
packer.writeArrayBegin(blocks.length * 3);
for(Block block : blocks) {
packer.write(block);
}
packer.writeArrayEnd();
packer.write(blocks);
packer.writeArrayEnd();
}
@ -50,15 +44,13 @@ public class ChunkTemplate extends AbstractTemplate<Chunk> {
int y = unpacker.readInt();
int width = unpacker.readInt();
int height = unpacker.readInt();
unpacker.readArrayBegin();
int numBlocks = width * height;
Block[] blocks = unpacker.read(Block[].class);
Chunk chunk = new Chunk(x, y, width, height);
for(int i = 0; i < numBlocks; i++) {
chunk.setBlock(i, unpacker.read(Block.class));
for(int i = 0; i < blocks.length; i++) {
chunk.setBlock(i, blocks[i]);
}
unpacker.readArrayEnd();
unpacker.readArrayEnd();
return chunk;
}

View file

@ -0,0 +1,50 @@
package brainwine.gameserver.msgpack.templates;
import java.io.IOException;
import org.msgpack.MessageTypeException;
import org.msgpack.packer.Packer;
import org.msgpack.template.AbstractTemplate;
import org.msgpack.unpacker.Unpacker;
import brainwine.gameserver.prefab.Prefab;
import brainwine.gameserver.zone.Block;
public class PrefabTemplate extends AbstractTemplate<Prefab> {
@Override
public void write(Packer packer, Prefab prefab, boolean required) throws IOException {
if(prefab == null) {
if(required) {
throw new MessageTypeException("Attempted to write null");
}
packer.writeNil();
return;
}
packer.write(prefab.getWidth());
packer.write(prefab.getHeight());
packer.write(prefab.getBlocks());
}
@Override
public Prefab read(Unpacker unpacker, Prefab to, boolean required) throws IOException {
if(!required && unpacker.trySkipNil()) {
return null;
}
int width = unpacker.readInt();
int height = unpacker.readInt();
Block[] blocks = unpacker.read(Block[].class);
if(to != null) {
to.setWidth(width);
to.setHeight(height);
to.setBlocks(blocks);
return to;
}
return new Prefab(width, height, blocks);
}
}

View file

@ -0,0 +1,31 @@
package brainwine.gameserver.prefab;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import brainwine.gameserver.item.Item;
@JsonIgnoreProperties(ignoreUnknown = true)
public class CorrespondingReplacement {
@JsonProperty("key")
private Item key;
@JsonProperty("values")
private Map<Item, Item> values = new HashMap<>();
@JsonCreator
private CorrespondingReplacement() {}
public Item getKey() {
return key;
}
public Map<Item, Item> getValues() {
return values;
}
}

View file

@ -0,0 +1,132 @@
package brainwine.gameserver.prefab;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.item.Item;
import brainwine.gameserver.util.WeightedList;
import brainwine.gameserver.zone.Block;
@JsonIgnoreProperties(ignoreUnknown = true)
public class Prefab {
@JsonProperty("dungeon")
private boolean dungeon;
@JsonProperty("ruin")
private boolean ruin;
@JsonProperty("loot")
private boolean loot;
@JsonProperty("decay")
private boolean decay;
@JsonProperty("mirrorable")
private boolean mirrorable;
@JsonProperty("replace")
private Map<Item, WeightedList<Item>> replacements = new HashMap<>();
@JsonProperty("corresponding_replace")
private Map<Item, CorrespondingReplacement> correspondingReplacements = new HashMap<>();
@JsonProperty("metadata")
private Map<Integer, Map<String, Object>> metadata = new HashMap<>();
@JsonIgnore
private int width;
@JsonIgnore
private int height;
@JsonIgnore
private Block[] blocks;
@JsonCreator
private Prefab() {}
@JsonIgnore
public Prefab(int width, int height, Block[] blocks) {
this(width, height, blocks, new HashMap<>());
}
@JsonIgnore
public Prefab(int width, int height, Block[] blocks, Map<Integer, Map<String, Object>> metadata) {
this.width = width;
this.height = height;
this.blocks = blocks;
this.metadata = metadata;
}
@JsonCreator
private static Prefab fromName(String name) {
return GameServer.getInstance().getPrefabManager().getPrefab(name);
}
public boolean isDungeon() {
return dungeon;
}
public boolean isRuin() {
return ruin;
}
public boolean hasLoot() {
return loot;
}
public boolean hasDecay() {
return decay;
}
public boolean isMirrorable() {
return mirrorable;
}
public Map<String, Object> getMetadata(int index) {
return metadata.get(index);
}
public Map<Integer, Map<String, Object>> getMetadata() {
return metadata;
}
public Map<Item, WeightedList<Item>> getReplacements() {
return replacements;
}
public Map<Item, CorrespondingReplacement> getCorrespondingReplacements() {
return correspondingReplacements;
}
public void setWidth(int width) {
this.width = width;
}
public int getWidth() {
return width;
}
public void setHeight(int height) {
this.height = height;
}
public int getHeight() {
return height;
}
public void setBlocks(Block[] blocks) {
this.blocks = blocks;
}
public Block[] getBlocks() {
return blocks;
}
}

View file

@ -0,0 +1,98 @@
package brainwine.gameserver.prefab;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.msgpack.unpacker.BufferUnpacker;
import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import com.fasterxml.jackson.databind.ObjectMapper;
import brainwine.gameserver.msgpack.MessagePackHelper;
public class PrefabManager {
private static final Logger logger = LogManager.getLogger();
private final File dataDir = new File("prefabs");
private final ObjectMapper mapper = new ObjectMapper();
private final Map<String, Prefab> prefabs = new HashMap<>();
public PrefabManager() {
loadPrefabs();
}
private void loadPrefabs() {
logger.info("Loading prefabs ...");
if(!dataDir.exists()) {
logger.info("Copying default prefabs ...");
dataDir.mkdirs();
Reflections reflections = new Reflections("prefabs", new ResourcesScanner());
Set<String> fileNames = reflections.getResources(x -> true);
for(String fileName : fileNames) {
File outputFile = new File(fileName);
outputFile.getParentFile().mkdirs();
try {
Files.copy(PrefabManager.class.getResourceAsStream(String.format("/%s", fileName)), outputFile.toPath());
} catch (IOException e) {
logger.error("Could not copy default prefabs", e);
}
}
}
File[] files = dataDir.listFiles();
for(File file : files) {
if(file.isDirectory()) {
loadPrefab(file);
}
}
logger.info("Successfully loaded {} prefab(s)", prefabs.size());
}
private void loadPrefab(File file) {
String name = file.getName();
File configFile = new File(file, "config.json");
try {
Prefab prefab = mapper.readValue(configFile, Prefab.class);
BufferUnpacker unpacker = MessagePackHelper.readFile(new File(file, "blocks.cmp"));
unpacker.read(prefab);
unpacker.close();
prefabs.put(name, prefab);
} catch(Exception e) {
logger.error("Could not load prefab {}:", name, e);
}
}
public void registerPrefab(String name, Prefab structure) throws Exception {
if(prefabs.containsKey(name)) {
logger.warn("Duplicate prefab name: {}", name);
return;
}
savePrefab(name, structure);
prefabs.put(name, structure);
}
private void savePrefab(String name, Prefab structure) throws Exception {
File outputDir = new File(dataDir, name);
outputDir.mkdirs();
MessagePackHelper.writeToFile(new File(outputDir, "blocks.cmp"), structure);
mapper.writerWithDefaultPrettyPrinter().writeValue(new File(outputDir, "config.json"), structure);
}
public Prefab getPrefab(String name) {
return prefabs.get(name);
}
}

View file

@ -1,18 +1,25 @@
package brainwine.gameserver.server.requests;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import brainwine.gameserver.GameServer;
import brainwine.gameserver.entity.player.NotificationType;
import brainwine.gameserver.entity.player.Player;
import brainwine.gameserver.item.Item;
import brainwine.gameserver.item.ItemUseType;
import brainwine.gameserver.item.Layer;
import brainwine.gameserver.loot.Loot;
import brainwine.gameserver.loot.LootManager;
import brainwine.gameserver.msgpack.models.BlockUseData;
import brainwine.gameserver.server.OptionalField;
import brainwine.gameserver.server.PlayerRequest;
import brainwine.gameserver.util.MapHelper;
import brainwine.gameserver.zone.Block;
import brainwine.gameserver.zone.MetaBlock;
import brainwine.gameserver.zone.Zone;
@SuppressWarnings("unchecked")
@ -33,77 +40,140 @@ public class BlockUseRequest extends PlayerRequest {
return;
}
Object[] data = this.data == null ? null : this.data.getData();
Block block = zone.getBlock(x, y);
MetaBlock metaBlock = zone.getMetaBlock(x, y);
Item item = block.getItem(layer);
int mod = block.getMod(layer);
if(data == null) {
if(item.hasUse(ItemUseType.CHANGE)) {
zone.updateBlock(x, y, layer, item, mod == 0 ? 1 : 0, player);
}
} else {
Object[] data = this.data.getData();
item.getUses().forEach((k, v) -> {
switch(k) {
case DIALOG:
case CREATE_DIALOG:
// TODO rework dialog system and clean this mess up
Map<String, Object> config = (Map<String, Object>)v;
String target = MapHelper.getString(config, "target");
for(Entry<ItemUseType, Object> entry : item.getUses().entrySet()) {
ItemUseType use = entry.getKey();
Object value = entry.getValue();
switch(use) {
case DIALOG:
case CREATE_DIALOG:
if(metaBlock != null && player.getDocumentId().equals(metaBlock.getOwner()) && data != null && value instanceof Map) {
Map<String, Object> config = (Map<String, Object>)value;
String target = MapHelper.getString(config, "target", "none");
switch(target) {
case "meta":
Map<String, Object> metadata = new HashMap<>();
List<Map<String, Object>> sections = MapHelper.getList(config, "sections");
int i = 0;
for(Map<String, Object> section : sections) {
metadata.put(MapHelper.getString(section, "input.key"), data[i++]);
if(sections != null && data.length == sections.size()) {
for(int i = 0; i < sections.size(); i++) {
Map<String, Object> section = sections.get(i);
String key = MapHelper.getString(section, "input.key");
if(key != null) {
metadata.put(key, data[i]);
} else if(MapHelper.getBoolean(section, "input.mod")) {
List<Object> options = MapHelper.getList(section, "input.options");
if(options != null) {
mod = options.indexOf(data[i]);
mod = mod == -1 ? 0 : mod;
mod *= MapHelper.getInt(section, "input.mod_multiple", 1);
zone.updateBlock(x, y, layer, item, mod, player);
}
}
}
}
// TODO find out what this is for
if(use == ItemUseType.CREATE_DIALOG) {
metadata.put("cd", true);
}
zone.setMetaBlock(x, y, item, player, metadata);
break;
default:
break;
}
break;
case TELEPORT:
if(mod == 1 && data.length == 2 && data[0] instanceof Integer && data[1] instanceof Integer) {
int tX = (int)data[0];
int tY = (int)data[1];
Block targetBlock = zone.getBlock(tX, tY);
}
break;
case CHANGE:
zone.updateBlock(x, y, layer, item, mod == 0 ? 1 : 0, player);
break;
case CONTAINER:
if(metaBlock != null) {
Map<String, Object> metadata = metaBlock.getMetadata();
String specialItem = MapHelper.getString(metadata, "$");
if(specialItem != null) {
String dungeonId = MapHelper.getString(metadata, "@");
if(targetBlock != null) {
Item targetItem = targetBlock.getFrontItem();
if(dungeonId != null && item.hasUse(ItemUseType.FIELDABLE) && zone.isDungeonIntact(dungeonId)) {
player.alert("This container is secured by protectors in the area.");
break;
}
if(specialItem.equals("?")) {
metadata.remove("$");
LootManager lootManager = GameServer.getInstance().getLootManager();
Loot loot = lootManager.getRandomLoot(15, zone.getBiome(), item.getLootCategories()); // TODO level
if(targetItem.hasUse(ItemUseType.TELEPORT, ItemUseType.ZONE_TELEPORT)) {
player.teleport(tX + 1, tY);
if(loot == null) {
player.alert("How quaint, this container is empty!");
} else {
player.awardLoot(loot, item.getLootGraphic());
}
} else {
player.alert("Sorry, this container can't be looted right now.");
}
if(mod != 0) {
zone.updateBlock(x, y, Layer.FRONT, item, 0);
}
}
}
break;
case TELEPORT:
if(data != null && mod == 1 && data.length == 2 && data[0] instanceof Integer && data[1] instanceof Integer) {
int tX = (int)data[0];
int tY = (int)data[1];
MetaBlock target = zone.getMetaBlock(tX, tY);
if(target != null && target.getItem().hasUse(ItemUseType.TELEPORT, ItemUseType.ZONE_TELEPORT)) {
player.teleport(tX + 1, tY);
}
} else if(mod == 0) {
zone.updateBlock(x, y, layer, item, 1);
player.notify("You repaired a teleporter!", NotificationType.ACCOMPLISHMENT);
player.notifyPeers(String.format("%s repaired a teleporter.", player.getName()), NotificationType.SYSTEM);
}
break;
case SWITCH:
if(data == null) {
if(metaBlock != null) {
// TODO timed switches
zone.updateBlock(x, y, layer, item, mod % 2 == 0 ? mod + 1 : mod - 1, player, null);
Map<String, Object> metadata = metaBlock.getMetadata();
List<List<Integer>> positions = MapHelper.getList(metadata, ">", Collections.emptyList());
for(List<Integer> position : positions) {
int sX = position.get(0);
int sY = position.get(1);
Block target = zone.getBlock(sX, sY);
if(target != null) {
Item switchedItem = target.getFrontItem();
if(switchedItem.hasUse(ItemUseType.SWITCHED)) {
if(!(item.getUse(ItemUseType.SWITCHED) instanceof String)) {
int switchedMod = target.getFrontMod();
zone.updateBlock(sX, sY, Layer.FRONT, switchedItem, switchedMod % 2 == 0 ? switchedMod + 1 : switchedMod - 1, null);
}
}
}
}
}
break;
default:
break;
}
});
}
/*
else if(data.hasMetadata()) {
// TODO
Item item = zone.getBlock(x, y).getItem(layer);
Map<String, Object> metadata = new HashMap<>();
metadata.putAll(data.getMetadata());
zone.setMetaBlock(x, y, item, player, metadata);
} else if(data.hasPosition()) {
int[] position = data.getPosition();
int tX = position[0];
int tY = position[1];
Item item = zone.getBlock(tX, tY).getItem(layer);
if(item.hasUse(ItemUseType.TELEPORT, ItemUseType.ZONE_TELEPORT)) {
player.teleport(tX + 1, tY);
break;
default:
break;
}
}*/
}
}
}

View file

@ -0,0 +1,43 @@
package brainwine.gameserver.util;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class Vector2i {
private int x;
private int y;
public Vector2i(int x, int y) {
this.x = x;
this.y = y;
}
@JsonCreator
private Vector2i(int[] positions) {
if(positions.length == 2) {
x = positions[0];
y = positions[1];
} else {
x = 1;
y = 1;
}
}
public void setX(int x) {
this.x = x;
}
public int getX() {
return x;
}
public void setY(int y) {
this.y = y;
}
public int getY() {
return y;
}
}

View file

@ -14,6 +14,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.function.Predicate;
@ -37,7 +38,9 @@ import brainwine.gameserver.item.ItemRegistry;
import brainwine.gameserver.item.ItemUseType;
import brainwine.gameserver.item.Layer;
import brainwine.gameserver.item.MetaType;
import brainwine.gameserver.item.ModType;
import brainwine.gameserver.msgpack.MessagePackHelper;
import brainwine.gameserver.prefab.Prefab;
import brainwine.gameserver.server.Message;
import brainwine.gameserver.server.messages.BlockChangeMessage;
import brainwine.gameserver.server.messages.BlockMetaMessage;
@ -248,6 +251,174 @@ public class Zone {
return false;
}
public Prefab chop(int x, int y, int width, int height) {
if(!areCoordinatesInBounds(x, y) || !areCoordinatesInBounds(x + width, y + height)) {
return null;
}
Block[] blocks = new Block[width * height];
Map<Integer, Map<String, Object>> metadata = new HashMap<>();
for(int i = 0; i < width; i++) {
for(int j = 0; j < height; j++) {
int index = j * width + i;
Block block = getBlock(x + i, y + j);
blocks[index] = new Block(block.getBaseItem(), block.getBackItem(), block.getBackMod(), block.getFrontItem(), block.getFrontMod(), block.getLiquidItem(), block.getLiquidMod());
MetaBlock metaBlock = metaBlocks.get(getBlockIndex(x + i, j + y));
if(metaBlock != null) {
Map<String, Object> data = MapHelper.copy(metaBlock.getMetadata());
if(!data.isEmpty()) {
List<List<Integer>> positions = MapHelper.getList(data, ">", Collections.emptyList());
for(List<Integer> position : positions) {
position.set(0, position.get(0) - x);
position.set(1, position.get(1) - y);
}
metadata.put(index, data);
}
}
}
}
return new Prefab(width, height, blocks, metadata);
}
public void placePrefab(Prefab prefab, int x, int y) {
placePrefab(prefab, x, y, new Random());
}
public void placePrefab(Prefab prefab, int x, int y, Random random) {
int width = prefab.getWidth();
int height = prefab.getHeight();
Block[] blocks = prefab.getBlocks();
int guardBlocks = 0;
String dungeonId = prefab.isDungeon() ? UUID.randomUUID().toString() : null;
boolean decay = prefab.hasDecay();
boolean mirrored = prefab.isMirrorable() && random.nextBoolean();
Map<Item, Item> replacedItems = new HashMap<>();
// Replacements
prefab.getReplacements().forEach((item, list) -> {
replacedItems.put(item, list.next(random));
});
// Corresponding replacements
prefab.getCorrespondingReplacements().forEach((item, data) -> {
Item keyReplacement = replacedItems.get(data.getKey());
if(keyReplacement != null) {
Item replacement = data.getValues().get(keyReplacement);
if(replacement != null) {
replacedItems.put(item, replacement);
}
}
});
for(int i = 0; i < width; i++) {
for(int j = 0; j < height; j++) {
int index = j * width + (mirrored ? width - 1 - i : i);
Block block = blocks[index];
Item baseItem = replacedItems.getOrDefault(block.getBaseItem(), block.getBaseItem());
Item backItem = replacedItems.getOrDefault(block.getBackItem(), block.getBackItem());
Item frontItem = replacedItems.getOrDefault(block.getFrontItem(), block.getFrontItem());
Item liquidItem = replacedItems.getOrDefault(block.getLiquidItem(), block.getLiquidItem());
int backMod = block.getBackMod();
int frontMod = block.getFrontMod();
int liquidMod = block.getLiquidMod();
// Update base item if it isn't empty
if(!baseItem.isAir()) {
updateBlock(x + i, y + j, Layer.BASE, baseItem);
}
// Update back item if it isn't empty
if(!backItem.isAir()) {
// Apply decay to back block
if(decay && backItem.getMod() == ModType.DECAY && random.nextBoolean()) {
backMod = random.nextInt(4) + 1;
}
updateBlock(x + i, y + j, Layer.BACK, backItem, backMod);
}
// Update front item if either the back, front or liquid item isn't empty
if(!backItem.isAir() || !frontItem.isAir() || !liquidItem.isAir()) {
// Apply mods
if(mirrored && frontItem.getMod() == ModType.ROTATION) {
// If rotation == mirror, swap mods 0 and 4, otherwise 1 and 3
if(frontItem.isMirrorable()) {
frontMod = frontMod == 0 ? 4 : frontMod == 4 ? 0 : frontMod;
} else {
frontMod = frontMod == 1 ? 3 : frontMod == 3 ? 1 : frontMod;
}
} else if(decay && frontItem.getMod() == ModType.DECAY && random.nextBoolean()) {
frontMod = random.nextInt(4) + 1;
}
int offset = mirrored ? -(frontItem.getBlockWidth() - 1) : 0;
// Clear the block it would normally occupy
if(offset != 0) {
updateBlock(x + i, y + j, Layer.FRONT, 0);
}
Map<String, Object> metadata = prefab.getMetadata(index);
metadata = metadata == null ? new HashMap<>() : MapHelper.copy(metadata);
// Add dungeon id to guard blocks and containers, and increment guard block count if applicable
if(dungeonId != null && frontItem.hasUse(ItemUseType.CONTAINER, ItemUseType.GUARD)) {
metadata.put("@", dungeonId);
if(frontItem.hasUse(ItemUseType.GUARD)) {
guardBlocks++;
}
}
// Determine lootability for containers
if(prefab.hasLoot() && frontItem.hasUse(ItemUseType.CONTAINER)) {
// If the container is a "high end" container, make it lootable. Otherwise 10% chance.
if(frontItem.hasUse(ItemUseType.FIELDABLE) || random.nextDouble() <= 0.1) {
metadata.put("$", "?");
frontMod = 1;
}
}
// Block is linked, offset positions
if(metadata.containsKey(">")) {
List<List<Integer>> positions = MapHelper.getList(metadata, ">", Collections.emptyList());
for(List<Integer> position : positions) {
int pX = position.get(0);
int pY = position.get(1);
// Create an offset in case the block is bigger than 1x1
Item linkedItem = blocks[pY * width + pX].getFrontItem();
linkedItem = replacedItems.getOrDefault(linkedItem, linkedItem);
int pOffset = -(linkedItem.getBlockWidth() - 1);
position.set(0, (mirrored ? width - 1 - pX + pOffset : pX) + x);
position.set(1, position.get(1) + y);
}
}
updateBlock(x + i + offset, y + j, Layer.FRONT, frontItem, frontMod, null, metadata);
}
// Update liquid item if it isn't empty
if(!liquidItem.isAir()) {
updateBlock(x + i, y + j, Layer.LIQUID, liquidItem, liquidMod);
}
}
}
if(guardBlocks > 0) {
dungeons.put(dungeonId, guardBlocks);
}
}
private void indexDungeons() {
List<MetaBlock> guardBlocks = getMetaBlocksWithUse(ItemUseType.GUARD);

View file

@ -10,6 +10,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import brainwine.gameserver.item.Item;
import brainwine.gameserver.prefab.Prefab;
import brainwine.gameserver.util.Vector2i;
import brainwine.gameserver.util.WeightedList;
import brainwine.gameserver.zone.gen.models.BaseResourceType;
import brainwine.gameserver.zone.gen.models.Deposit;
@ -28,6 +30,21 @@ public class GeneratorConfig {
@JsonProperty("speleothems")
private Item[] speleothems = {};
@JsonProperty("unique_structures")
private Prefab[] uniqueStructures = {};
@JsonProperty("dungeons")
private WeightedList<Prefab> dungeons = new WeightedList<>();
@JsonProperty("spawn_towers")
private WeightedList<Prefab> spawnTowers = new WeightedList<>();
@JsonProperty("dungeon_region")
private Vector2i dungeonRegion = new Vector2i(80, 64);
@JsonProperty("dungeon_chance")
private double dungeonRate = 0.25;
@JsonProperty("stone_variants")
private WeightedList<StoneVariant> stoneVariants = new WeightedList<>();
@ -53,6 +70,26 @@ public class GeneratorConfig {
return speleothems;
}
public Prefab[] getUniqueStructures() {
return uniqueStructures;
}
public WeightedList<Prefab> getDungeons() {
return dungeons;
}
public WeightedList<Prefab> getSpawnTowers() {
return spawnTowers;
}
public Vector2i getDungeonRegion() {
return dungeonRegion;
}
public double getDungeonRate() {
return dungeonRate;
}
public WeightedList<StoneVariant> getStoneVariants() {
return stoneVariants;
}

View file

@ -1,18 +1,24 @@
package brainwine.gameserver.zone.gen;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import brainwine.gameserver.item.Item;
import brainwine.gameserver.item.Layer;
import brainwine.gameserver.prefab.Prefab;
import brainwine.gameserver.zone.Chunk;
import brainwine.gameserver.zone.Zone;
import brainwine.gameserver.zone.gen.models.BlockPosition;
import brainwine.gameserver.zone.gen.models.Cave;
public class GeneratorContext {
private final List<Cave> caves = new ArrayList<>();
private final Map<BlockPosition, Prefab> prefabs = new HashMap<>();
private final Zone zone;
private final Random random;
private final int seed;
@ -39,6 +45,28 @@ public class GeneratorContext {
return caves;
}
public void placePrefab(Prefab prefab, int x, int y) {
if(!willPrefabOverlap(prefab, x, y)) {
zone.placePrefab(prefab, x, y, random);
prefabs.put(new BlockPosition(x, y), prefab);
}
}
public boolean willPrefabOverlap(Prefab prefab, int x, int y) {
for(Entry<BlockPosition, Prefab> entry : prefabs.entrySet()) {
BlockPosition position = entry.getKey();
Prefab other = entry.getValue();
int x2 = position.getX();
int y2 = position.getY();
if(x + prefab.getWidth() >= x2 && x <= x2 + other.getWidth() && y + prefab.getHeight() >= y2 && y <= y2 + other.getHeight()) {
return true;
}
}
return false;
}
public void updateBlock(int x, int y, Layer layer, int item) {
updateBlock(x, y, layer, item, 0);
}

View file

@ -0,0 +1,87 @@
package brainwine.gameserver.zone.gen;
import brainwine.gameserver.item.Layer;
import brainwine.gameserver.prefab.Prefab;
import brainwine.gameserver.util.Vector2i;
import brainwine.gameserver.util.WeightedList;
import brainwine.gameserver.zone.Block;
public class StructureGenerator implements GeneratorTask {
private final Prefab[] uniqueStructures;
private final WeightedList<Prefab> dungeons;
private final WeightedList<Prefab> spawnTowers;
private final Vector2i dungeonRegion;
private final double dungeonRate;
public StructureGenerator(GeneratorConfig config) {
uniqueStructures = config.getUniqueStructures();
dungeons = config.getDungeons();
spawnTowers = config.getSpawnTowers();
dungeonRegion = config.getDungeonRegion();
dungeonRate = config.getDungeonRate();
}
@Override
public void generate(GeneratorContext ctx) {
int width = ctx.getWidth();
int height = ctx.getHeight();
for(Prefab structure : uniqueStructures) {
int x = ctx.nextInt(width - 2) + 1;
int minY = ctx.getZone().getSurface()[x];
int y = ctx.nextInt(height - minY - 2) + minY;
ctx.placePrefab(structure, x, y);
}
if(!dungeons.isEmpty()) {
for(int x = 0; x < width; x += dungeonRegion.getX()) {
for(int y = 0; y < height; y += dungeonRegion.getY()) {
if(ctx.nextDouble() <= dungeonRate) {
Prefab dungeon = dungeons.next(ctx.getRandom());
int prefabWidth = dungeon.getWidth();
int prefabHeight = dungeon.getHeight();
if(ctx.isUnderground(x, y) && ctx.isUnderground(x + prefabWidth, y)) {
int placeX = x + (prefabWidth >= dungeonRegion.getX() ? x : ctx.nextInt(dungeonRegion.getX() - prefabWidth));
placeX = Math.max(1, Math.min(placeX, width - prefabWidth - 1));
int placeY = y + (prefabHeight >= dungeonRegion.getY() ? y : ctx.nextInt(dungeonRegion.getY() - prefabHeight));
placeY = Math.max(1, Math.min(placeY, height - prefabHeight - 2));
ctx.placePrefab(dungeon, placeX, placeY);
}
}
}
}
}
placeRandomSpawnTower(ctx, (int)(width * 0.2));
placeRandomSpawnTower(ctx, (int)(width * 0.5));
placeRandomSpawnTower(ctx, (int)(width * 0.8));
}
private void placeRandomSpawnTower(GeneratorContext ctx, int x) {
int surface = ctx.getZone().getSurface()[x];
if(!spawnTowers.isEmpty()) {
Prefab spawnTower = spawnTowers.next(ctx.getRandom());
int height = spawnTower.getHeight();
int y = surface - height;
ctx.placePrefab(spawnTower, x, y);
generateFoundation(ctx, x, y + height, spawnTower.getWidth());
} else {
ctx.updateBlock(x, surface - 1, Layer.FRONT, 891, 0);
}
}
private void generateFoundation(GeneratorContext ctx, int x, int y, int width) {
for(int i = x; i < x + width; i++) {
int j = y;
Block block = null;
while((block = ctx.getZone().getBlock(i, j)) != null && block.getBaseItem().isAir() && !block.getFrontItem().isWhole()) {
ctx.updateBlock(i, j, Layer.BACK, 258);
j++;
}
}
}
}

View file

@ -7,6 +7,7 @@ public class ZoneGenerator {
private final GeneratorTask terrainGenerator;
private final GeneratorTask caveGenerator;
private final GeneratorTask decorGenerator;
private final GeneratorTask structureGenerator;
public ZoneGenerator() {
this(new GeneratorConfig());
@ -16,6 +17,7 @@ public class ZoneGenerator {
terrainGenerator = new TerrainGenerator(config);
caveGenerator = new CaveGenerator(config);
decorGenerator = new DecorGenerator(config);
structureGenerator = new StructureGenerator(config);
}
public void generate(GeneratorContext ctx) {
@ -25,7 +27,7 @@ public class ZoneGenerator {
terrainGenerator.generate(ctx);
caveGenerator.generate(ctx);
decorGenerator.generate(ctx);
ctx.updateBlock(width / 2, ctx.getZone().getSurface()[width / 2] - 1, Layer.FRONT, 891, 0); // TODO structures
structureGenerator.generate(ctx);
for(int x = 0; x < width; x++) {
ctx.updateBlock(x, height - 1, Layer.FRONT, 666, 0);

View file

@ -15,6 +15,34 @@
"sandstone": 4,
"limestone": 2
},
"unique_structures": [
"paint_bunker",
"head_bunker"
],
"dungeons": [
"dungeon_large_1",
"dungeon_large_2",
"dungeon_medium_1",
"dungeon_medium_2",
"dungeon_medium_3",
"dungeon_medium_4",
"dungeon_medium_5",
"dungeon_medium_6",
"dungeon_medium_7",
"dungeon_medium_8",
"dungeon_medium_9",
"dungeon_medium_10",
"dungeon_small_1",
"dungeon_small_2",
"dungeon_small_3",
"dungeon_small_4"
],
"spawn_towers": [
"plain_spawn_tower_1",
"plain_spawn_tower_2",
"plain_spawn_tower_3",
"plain_spawn_tower_4"
],
"base_resources": {
"clay": {
"per": 1200
@ -197,6 +225,29 @@
"default": 6,
"limestone": 1
},
"unique_structures": [
"paint_bunker",
"head_bunker"
],
"dungeons": [
"dungeon_large_1",
"dungeon_large_2",
"dungeon_medium_1",
"dungeon_medium_2",
"dungeon_medium_3",
"dungeon_medium_4",
"dungeon_medium_5",
"dungeon_medium_6",
"dungeon_medium_7",
"dungeon_medium_8",
"dungeon_medium_9",
"dungeon_medium_10",
"dungeon_small_1",
"dungeon_small_2",
"dungeon_small_3",
"dungeon_small_4"
],
"dungeon_chance": 0.333,
"base_resources": {
"clay": {
"per": 1200
@ -362,6 +413,29 @@
"ground/stalagmite-4",
"ground/stalagmite-5"
],
"unique_structures": [
"paint_bunker",
"head_bunker"
],
"dungeons": [
"dungeon_large_1",
"dungeon_large_2",
"dungeon_medium_1",
"dungeon_medium_2",
"dungeon_medium_3",
"dungeon_medium_4",
"dungeon_medium_5",
"dungeon_medium_6",
"dungeon_medium_7",
"dungeon_medium_8",
"dungeon_medium_9",
"dungeon_medium_10",
"dungeon_small_1",
"dungeon_small_2",
"dungeon_small_3",
"dungeon_small_4"
],
"dungeon_chance": 0.375,
"base_resources": {
"clay": {
"per": 1500
@ -489,6 +563,29 @@
"default": 6,
"limestone": 1
},
"unique_structures": [
"paint_bunker",
"head_bunker"
],
"dungeons": [
"dungeon_large_1",
"dungeon_large_2",
"dungeon_medium_1",
"dungeon_medium_2",
"dungeon_medium_3",
"dungeon_medium_4",
"dungeon_medium_5",
"dungeon_medium_6",
"dungeon_medium_7",
"dungeon_medium_8",
"dungeon_medium_9",
"dungeon_medium_10",
"dungeon_small_1",
"dungeon_small_2",
"dungeon_small_3",
"dungeon_small_4"
],
"dungeon_chance": 0.4,
"base_resources": {
"rocks": {
"per": 200
@ -608,6 +705,29 @@
"default": 3,
"sandstone": 1
},
"unique_structures": [
"paint_bunker",
"head_bunker"
],
"dungeons": [
"dungeon_large_1",
"dungeon_large_2",
"dungeon_medium_1",
"dungeon_medium_2",
"dungeon_medium_3",
"dungeon_medium_4",
"dungeon_medium_5",
"dungeon_medium_6",
"dungeon_medium_7",
"dungeon_medium_8",
"dungeon_medium_9",
"dungeon_medium_10",
"dungeon_small_1",
"dungeon_small_2",
"dungeon_small_3",
"dungeon_small_4"
],
"dungeon_chance": 0.333,
"base_resources": {
"clay": {
"per": 800
@ -748,6 +868,30 @@
"default": 17,
"limestone": 4
},
"unique_structures": [
"paint_bunker",
"head_bunker"
],
"dungeons": [
"dungeon_large_1",
"dungeon_large_2",
"dungeon_medium_1",
"dungeon_medium_2",
"dungeon_medium_3",
"dungeon_medium_4",
"dungeon_medium_5",
"dungeon_medium_6",
"dungeon_medium_7",
"dungeon_medium_8",
"dungeon_medium_9",
"dungeon_medium_10",
"dungeon_small_1",
"dungeon_small_2",
"dungeon_small_3",
"dungeon_small_4"
],
"dungeon_region": [81, 85],
"dungeon_chance": 0.4,
"base_resources": {
"clay": {
"per": 1200
@ -869,6 +1013,30 @@
"ground/stalagmite-4",
"ground/stalagmite-5"
],
"unique_structures": [
"paint_bunker",
"head_bunker"
],
"dungeons": [
"dungeon_large_1",
"dungeon_large_2",
"dungeon_medium_1",
"dungeon_medium_2",
"dungeon_medium_3",
"dungeon_medium_4",
"dungeon_medium_5",
"dungeon_medium_6",
"dungeon_medium_7",
"dungeon_medium_8",
"dungeon_medium_9",
"dungeon_medium_10",
"dungeon_small_1",
"dungeon_small_2",
"dungeon_small_3",
"dungeon_small_4"
],
"dungeon_region": [81, 81],
"dungeon_chance": 0.333,
"base_resources": {
"rocks": {
"per": 100

View file

@ -0,0 +1,24 @@
{
"dungeon": true,
"loot": true,
"decay": true,
"mirrorable": true,
"metadata": {
"2316": {
"cd": true,
"m": "",
">": [[ 49, 7 ]]
},
"2333": {
"cd": true,
"m": "",
">": [[ 46, 43 ]]
},
"1977": {
"cd": true,
"t": "10",
"m": "",
">": [[ 11, 39 ]]
}
}
}

View file

@ -0,0 +1,23 @@
{
"dungeon": true,
"loot": true,
"decay": true,
"mirrorable": true,
"metadata": {
"2306": {
"cd": true,
"m": "",
">": [[ 10, 29 ]]
},
"416": {
"cd": true,
"m": "",
">": [[ 36, 30 ]]
},
"2977": {
"cd": true,
"m": "",
">": [[ 35, 42 ]]
}
}
}

View file

@ -0,0 +1,13 @@
{
"dungeon": true,
"loot": true,
"decay": true,
"mirrorable": true,
"metadata": {
"1017": {
"cd": true,
"m": "",
">": [[ 11, 8 ]]
}
}
}

View file

@ -0,0 +1,19 @@
{
"dungeon": true,
"loot": true,
"decay": true,
"mirrorable": true,
"metadata": {
"909": {
"cd": true,
"m": "",
">": [[ 21, 28 ]]
},
"425": {
"cd": true,
"t": "10",
"m": "",
">": [[ 3, 22 ]]
}
}
}

View file

@ -0,0 +1,2 @@
xœí˜;RÃ0†e;T¤á %à
”P2i*†ÔÁãa u|<7C>('à.Á²-kWZ=-‡Ïd­ÈéûW«Õ:·7?W-uñò%«É~‰Q'ôùG:œŽlUn»;/ß »µà-ÀÈ˦Ÿ²±y¥†xÜLƈ91ìW.r#/.ùêÁB+)jçHK2vx röÎcÕ6)JjK .b^)p^~€ßðêIç†EÁlìžd£ÝäÓj<C393>ÑÖFÓújÄ¢c+n†àSs•—6<E28094>ƒ!/³áìÞ—a¦=ü8—˜J½ÙpU"roÃÄ@`:iÖbBÃäÕàY2<>˜…Œ×*Ø?FXwæÈªê~h &†òa?0%GjîN±ó«!x(Lj#5®CŒØù¯rÕêOú—¹º<å‹u%m¨:9r=<3D>š·ÖçíW{¶U±ÃEÂÐåÐÖL\µ®U|Êð±F(#:BáSÎ\83ÅÚXVòb ¿†‡%LÒ®.ûÍ+yd'öx/V™øhÕEÏ60F-Ißš3Šoë·˜M=gÕ&*å:Ð oŽð'z¦ó;Ìœs éâ<C3A9>%8ÒÙ*@rÐðÖ“.îïhf"nè® ´k[Î¥ª;¿::ð\¨qÁ¨ÚóÙųèwÒÍÙ™E™O<>up,`vQeŒ¯ï×z±ayÍKÕ!<´€ <0C>lÛ{bȺ/2pGðÔ¬:ã…^àPñ}ª€ËRíž™ÿÝõ·$ùcá ã™

View file

@ -0,0 +1,40 @@
{
"dungeon": true,
"loot": true,
"decay": true,
"mirrorable": true,
"replace": {
"building/roof-brown": [
"building/roof",
"building/roof-brown"
]
},
"corresponding_replace": {
"building/roof-edge": {
"key": "building/roof-brown",
"values": {
"building/roof": "building/roof-brown-edge",
"building/roof-brown": "building/roof-edge"
}
},
"building/roof-brown-edge": {
"key": "building/roof-brown",
"values": {
"building/roof": "building/roof-edge",
"building/roof-brown": "building/roof-brown-edge"
}
}
},
"metadata": {
"1220": {
"cd": true,
"m": "",
">": [[ 13, 17 ]]
},
"328": {
"cd": true,
"m": "",
">": [[ 23, 29 ]]
}
}
}

View file

@ -0,0 +1,29 @@
{
"dungeon": true,
"loot": true,
"decay": true,
"mirrorable": true,
"replace": {
"back/wallpaper-1": [
"back/wallpaper-1",
"back/wallpaper-2",
"back/wallpaper-3",
"back/wallpaper-4",
"back/wallpaper-5",
"back/wallpaper-6",
"back/wallpaper-7",
"back/wallpaper-8",
"back/wallpaper-9",
"back/wallpaper-10",
"back/wallpaper-11",
"back/wallpaper-12"
]
},
"metadata": {
"224": {
"cd": true,
"m": "",
">": [[ 30, 32 ]]
}
}
}

View file

@ -0,0 +1,60 @@
{
"dungeon": true,
"loot": true,
"decay": true,
"mirrorable": true,
"replace": {
"back/wallpaper-1": [
"back/wallpaper-1",
"back/wallpaper-2",
"back/wallpaper-3",
"back/wallpaper-4",
"back/wallpaper-5",
"back/wallpaper-6",
"back/wallpaper-7",
"back/wallpaper-8",
"back/wallpaper-9",
"back/wallpaper-10",
"back/wallpaper-11",
"back/wallpaper-12"
],
"back/wallpaper-2": [
"back/wallpaper-1",
"back/wallpaper-2",
"back/wallpaper-3",
"back/wallpaper-4",
"back/wallpaper-5",
"back/wallpaper-6",
"back/wallpaper-7",
"back/wallpaper-8",
"back/wallpaper-9",
"back/wallpaper-10",
"back/wallpaper-11",
"back/wallpaper-12"
],
"building/fence-iron-1": [
"building/fence-iron-1",
"building/fence-iron-2"
],
"building/roof": [
"building/roof",
"building/roof-brown"
]
},
"corresponding_replace": {
"building/roof-edge": {
"key": "building/roof",
"values": {
"building/roof": "building/roof-edge",
"building/roof-brown": "building/roof-brown-edge"
}
}
},
"metadata": {
"711": {
"cd": true,
"m": "",
">": [[ 9, 19 ]]
}
}
}

View file

@ -0,0 +1,19 @@
{
"dungeon": true,
"loot": true,
"decay": true,
"mirrorable": true,
"metadata": {
"708": {
"cd": true,
"t": "10",
"m": "",
">": [[ 19, 22 ]]
},
"1289": {
"cd": true,
"m": "",
">": [[ 11, 7 ]]
}
}
}

View file

@ -0,0 +1,43 @@
{
"dungeon": true,
"loot": true,
"decay": true,
"mirrorable": true,
"replace": {
"back/wallpaper-1": [
"back/wallpaper-1",
"back/wallpaper-2",
"back/wallpaper-3",
"back/wallpaper-4",
"back/wallpaper-5",
"back/wallpaper-6",
"back/wallpaper-7",
"back/wallpaper-8",
"back/wallpaper-9",
"back/wallpaper-10",
"back/wallpaper-11",
"back/wallpaper-12"
],
"back/wallpaper-2": [
"back/wallpaper-1",
"back/wallpaper-2",
"back/wallpaper-3",
"back/wallpaper-4",
"back/wallpaper-5",
"back/wallpaper-6",
"back/wallpaper-7",
"back/wallpaper-8",
"back/wallpaper-9",
"back/wallpaper-10",
"back/wallpaper-11",
"back/wallpaper-12"
]
},
"metadata": {
"1117": {
"cd": true,
"m": "",
">": [[ 24, 12 ]]
}
}
}

View file

@ -0,0 +1,19 @@
{
"dungeon": true,
"loot": true,
"decay": true,
"mirrorable": true,
"metadata": {
"835": {
"cd": true,
"t": "10",
"m": "",
">": [[ 40, 29 ]]
},
"1241": {
"cd": true,
"m": "",
">": [[ 18, 7 ]]
}
}
}

View file

@ -0,0 +1,29 @@
{
"dungeon": true,
"loot": true,
"decay": true,
"mirrorable": true,
"replace": {
"back/wallpaper-1": [
"back/wallpaper-1",
"back/wallpaper-2",
"back/wallpaper-3",
"back/wallpaper-4",
"back/wallpaper-5",
"back/wallpaper-6",
"back/wallpaper-7",
"back/wallpaper-8",
"back/wallpaper-9",
"back/wallpaper-10",
"back/wallpaper-11",
"back/wallpaper-12"
]
},
"metadata": {
"190": {
"cd": true,
"m": "",
">": [[ 15, 17 ]]
}
}
}

View file

@ -0,0 +1,56 @@
{
"dungeon": true,
"loot": true,
"decay": true,
"mirrorable": true,
"replace": {
"back/wallpaper-1": [
"back/wallpaper-1",
"back/wallpaper-2",
"back/wallpaper-3",
"back/wallpaper-4",
"back/wallpaper-5",
"back/wallpaper-6",
"back/wallpaper-7",
"back/wallpaper-8",
"back/wallpaper-9",
"back/wallpaper-10",
"back/wallpaper-11",
"back/wallpaper-12"
],
"back/wallpaper-2": [
"back/wallpaper-1",
"back/wallpaper-2",
"back/wallpaper-3",
"back/wallpaper-4",
"back/wallpaper-5",
"back/wallpaper-6",
"back/wallpaper-7",
"back/wallpaper-8",
"back/wallpaper-9",
"back/wallpaper-10",
"back/wallpaper-11",
"back/wallpaper-12"
],
"back/brick-mixed": [
"back/brick-mixed",
"back/brick-tan",
"back/brick-blue"
],
"ammo/bullets": [
"ammo/bullets",
"containers/crate-small"
],
"containers/sack-small": [
"containers/sack-small",
"building/pitch"
]
},
"metadata": {
"886": {
"cd": true,
"m": "",
">": [[ 23, 7 ]]
}
}
}

View file

@ -0,0 +1,3 @@
x<EFBFBD>э<EFBFBD>M<0E>0<10><>6<>xw&ю<>Иє.L<>КвЅё8Ф<38>7№&юЧx<18>ђгb C-<2D>YМ&CвїЭtR<74>ёш1И<31>ўAvБ'OЖнЕ<D0BD>8їF/<04> П<C2A0>И#<23><>RУВycБJ9NvB.њ+%P- 8$A>)XL<10>љМф;2MБФВpЪDэЃ$Дw№ј2M<32><4D>ьiШаiч<>с<EFBFBD>{ТпЯ<D0BF><D0AF>вЏ іj)ю}б<><D0B1>*/п2аЕЏИ{ПсмЦЈ"|ѓ@Хй<>.<2E><> чC<D187>и<EFBFBD>PЮB<D0AE>q
њk<EFBFBD>ЈmЉ(\ђзК"<22>uВ+<1B>
йБЎќЊднXВNљЬW6Ђ<36>\ІЁ5#ѓ<><D193>Q,<2C>є&В#R'йf"Ѓ<>К>a%<25> ;SYLХв<D0A5><D0B2>NQ§<51>jEP+OчзЉъ

View file

@ -0,0 +1,22 @@
{
"dungeon": true,
"loot": true,
"decay": true,
"mirrorable": true,
"replace": {
"back/wallpaper-1": [
"back/wallpaper-1",
"back/wallpaper-2",
"back/wallpaper-3",
"back/wallpaper-4",
"back/wallpaper-5",
"back/wallpaper-6",
"back/wallpaper-7",
"back/wallpaper-8",
"back/wallpaper-9",
"back/wallpaper-10",
"back/wallpaper-11",
"back/wallpaper-12"
]
}
}

View file

@ -0,0 +1,29 @@
{
"dungeon": true,
"loot": true,
"decay": true,
"mirrorable": true,
"metadata": {
"518": {
"cd": true,
"m": "",
">": [[ 7, 11 ]]
},
"786": {
"cd": true,
"t": "10",
"m": "",
">": [[ 21, 11 ]]
},
"799": {
"cd": true,
"m": "",
">": [[ 11, 11 ]]
},
"633": {
"cd": true,
"m": "",
">": [[ 9, 11 ]]
}
}
}

View file

@ -0,0 +1,6 @@
{
"dungeon": true,
"loot": true,
"decay": true,
"mirrorable": true
}

View file

@ -0,0 +1,6 @@
{
"dungeon": true,
"loot": true,
"decay": true,
"mirrorable": true
}

View file

@ -0,0 +1 @@
xœ½”A E§L»sጉÛFâ¸4&&Þǘô"®MÆxv€)N Êâ ¡Àÿ<C380>ógs sÑ£°}6:~‡RÌ><3E>X—ª5ˆÈÐ¥ª<C2A5>ÒåòlH¥N8“õË0q>J0øQå‰[q5âÜ„Yò»¢zgëWCæÀÂ<C380>Üõ Ä› Φö6GxâäBfEk­ žÃxÕÑž Ü_+úSÜ÷ÎwÒ¥^…X=Vé¿u|P…`!}e<><65>³â`p+w GbRÐK¡¦1Gg_? »`eÂ{¿©=òÁÐbtÖªX?©öÚ<>J

View file

@ -0,0 +1,43 @@
{
"loot": true,
"decay": true,
"mirrorable": true,
"replace": {
"furniture/head-deer": {
"furniture/head-predator": 1,
"furniture/head-tiger": 3,
"furniture/head-fish": 10,
"furniture/head-cthulhu": 50,
"furniture/head-bear": 80,
"furniture/head-deer": 80
},
"back/wallpaper-1": [
"back/wallpaper-1",
"back/wallpaper-2",
"back/wallpaper-3",
"back/wallpaper-4",
"back/wallpaper-5",
"back/wallpaper-6",
"back/wallpaper-7",
"back/wallpaper-8",
"back/wallpaper-9",
"back/wallpaper-10",
"back/wallpaper-11",
"back/wallpaper-12"
],
"building/pitch": [
"building/pitch",
"containers/crate-small",
"ammo/bullets"
],
"containers/crate-small": [
"containers/crate-small",
"ammo/bullets"
],
"containers/sack-small": [
"containers/sack-small",
"building/pitch",
"ammo/bullets"
]
}
}

View file

@ -0,0 +1,31 @@
{
"decay": true,
"mirrorable": true,
"replace": {
"furniture/painting-une-pipe": {
"furniture/painting-notch": 1,
"furniture/painting-outling": 3,
"furniture/solar-system-diorama": 3,
"furniture/painting-surfer": 8,
"furniture/airship-in-bottle": 8,
"furniture/painting-mayflower": 10,
"furniture/painting-autumn": 10,
"furniture/painting-leapquest": 22,
"furniture/painting-digdug": 22,
"furniture/painting-alpaca": 30,
"furniture/globe": 30,
"furniture/painting-northerners": 48,
"furniture/painting-smasheroid": 48,
"furniture/painting-gashlycrumb": 67,
"furniture/painting-son-of-man": 104,
"furniture/painting-une-pipe": 600
}
},
"metadata": {
"84": {
"cd": true,
"m": "",
">": [[ 14, 5 ]]
}
}
}

View file

@ -0,0 +1,38 @@
{
"decay": true,
"mirrorable": true,
"replace": {
"back/wallpaper-1": [
"back/wallpaper-1",
"back/wallpaper-2",
"back/wallpaper-3",
"back/wallpaper-4",
"back/wallpaper-5",
"back/wallpaper-6",
"back/wallpaper-7",
"back/wallpaper-8",
"back/wallpaper-9",
"back/wallpaper-10",
"back/wallpaper-11",
"back/wallpaper-12"
],
"building/ladder-brass": [
"building/ladder-wood",
"building/ladder-brass",
"building/ladder-rope"
],
"building/roof-brown": [
"building/roof",
"building/roof-brown"
]
},
"corresponding_replace": {
"building/roof-brown-edge": {
"key": "building/roof-brown",
"values": {
"building/roof": "building/roof-edge",
"building/roof-brown": "building/roof-brown-edge"
}
}
}
}

View file

@ -0,0 +1,32 @@
{
"decay": true,
"mirrorable": true,
"replace": {
"building/brick-tan": [
"building/brick",
"building/brick-mixed",
"building/brick-tan",
"building/brick-blue"
],
"building/ladder-wood": [
"building/ladder-wood",
"building/ladder-brass",
"building/ladder-rope"
],
"building/fence-iron-2": [
"building/fence-iron-1",
"building/fence-iron-2"
]
},
"corresponding_replace": {
"back/brick-tan": {
"key": "building/brick-tan",
"values": {
"building/brick": "back/brick",
"building/brick-mixed": "back/brick-mixed",
"building/brick-tan": "back/brick-tan",
"building/brick-blue": "back/brick-blue"
}
}
}
}

View file

@ -0,0 +1,46 @@
{
"decay": true,
"mirrorable": true,
"replace": {
"back/wallpaper-1": [
"back/wallpaper-1",
"back/wallpaper-2",
"back/wallpaper-3",
"back/wallpaper-4",
"back/wallpaper-5",
"back/wallpaper-6",
"back/wallpaper-7",
"back/wallpaper-8",
"back/wallpaper-9",
"back/wallpaper-10",
"back/wallpaper-11",
"back/wallpaper-12"
],
"building/brick-tan": [
"building/brick",
"building/brick-mixed",
"building/brick-tan",
"building/brick-blue"
],
"building/ladder-wood": [
"building/ladder-wood",
"building/ladder-brass",
"building/ladder-rope"
],
"building/fence-iron-2": [
"building/fence-iron-1",
"building/fence-iron-2"
]
},
"corresponding_replace": {
"back/brick-tan": {
"key": "building/brick-tan",
"values": {
"building/brick": "back/brick",
"building/brick-mixed": "back/brick-mixed",
"building/brick-tan": "back/brick-tan",
"building/brick-blue": "back/brick-blue"
}
}
}
}

View file

@ -0,0 +1,15 @@
{
"decay": true,
"mirrorable": true,
"replace": {
"building/fence-iron-2": [
"building/fence-iron-1",
"building/fence-iron-2"
],
"building/ladder-wood": [
"building/ladder-wood",
"building/ladder-brass",
"building/ladder-rope"
]
}
}