diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/BlockMetaMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/BlockMetaMessage.java index b666bae..49a9caa 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/BlockMetaMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/BlockMetaMessage.java @@ -1,9 +1,9 @@ package brainwine.gameserver.server.messages; -import java.util.HashMap; import java.util.Map; import brainwine.gameserver.server.Message; +import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.zone.MetaBlock; public class BlockMetaMessage extends Message { @@ -12,21 +12,14 @@ public class BlockMetaMessage extends Message { public int y; public Map metadata; - public BlockMetaMessage(int x, int y) { - this(x, y, new HashMap()); - } - - public BlockMetaMessage(MetaBlock metaBlock) { - this.x = metaBlock.getX(); - this.y = metaBlock.getY(); + public BlockMetaMessage(MetaBlock block) { + this.x = block.getX(); + this.y = block.getY(); + this.metadata = MapHelper.copy(block.getMetadata()); + this.metadata.put("i", block.getItem().getId()); - // Create a separate map that includes the item id and owner of the MetaBlock. - this.metadata = new HashMap<>(); - this.metadata.putAll(metaBlock.getMetadata()); - this.metadata.put("i", metaBlock.getItem().getId()); - - if(metaBlock.hasOwner()) { - this.metadata.put("p", metaBlock.getOwner()); + if(block.hasOwner()) { + this.metadata.put("p", block.getOwner()); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java b/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java index 350edec..0653546 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java @@ -1,58 +1,61 @@ package brainwine.gameserver.zone; -import java.beans.ConstructorProperties; import java.util.HashMap; import java.util.Map; -import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; -import brainwine.gameserver.item.ItemRegistry; +@JsonInclude(Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) public class MetaBlock { - private final int x; - private final int y; - private final Item item; + @JsonProperty("x") + private int x; + + @JsonProperty("y") + private int y; + + @JsonProperty("item") + private Item item; + + @JsonProperty("owner") private String owner; - private Map metadata; + + @JsonProperty("metadata") + private Map metadata = new HashMap<>(); + + @JsonCreator + private MetaBlock() {} public MetaBlock(int x, int y) { this(x, y, Item.AIR); } public MetaBlock(int x, int y, Item item) { - this(x, y, item, new HashMap()); + this(x, y, item, null); } public MetaBlock(int x, int y, Item item, Map metadata) { + this(x, y, item, null, metadata); + } + + public MetaBlock(int x, int y, Item item, Player owner, Map metadata) { this.x = x; this.y = y; this.item = item; + setOwner(owner); this.metadata = metadata; - setMetadata(metadata); } - @ConstructorProperties({"x", "y", "item", "owner", "metadata"}) - private MetaBlock(int x, int y, int item, String owner, Map metadata) { - this(x, y, ItemRegistry.getItem(item), metadata); - this.owner = owner; - } - - @JsonValue - public Map getJsonValue() { - Map map = new HashMap(); - map.put("x", x); - map.put("y", y); - map.put("item", item.getId()); - map.put("metadata", metadata); - - // Don't include owner if there isn't one. - if(hasOwner()) { - map.put("owner", owner); - } - - return map; + public void setOwner(Player owner) { + this.owner = owner == null ? null : owner.getDocumentId(); } public void setOwner(String owner) { diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 94a8a0a..0c2dafa 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -1,10 +1,12 @@ package brainwine.gameserver.zone; import java.beans.ConstructorProperties; +import java.io.File; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -14,11 +16,15 @@ import java.util.Map.Entry; import java.util.Queue; import java.util.Set; import java.util.UUID; +import java.util.function.Predicate; + +import org.msgpack.unpacker.BufferUnpacker; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIncludeProperties; import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.databind.ObjectMapper; import brainwine.gameserver.GameServer; import brainwine.gameserver.entity.Entity; @@ -30,6 +36,7 @@ import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; import brainwine.gameserver.item.MetaType; +import brainwine.gameserver.msgpack.MessagePackHelper; import brainwine.gameserver.server.Message; import brainwine.gameserver.server.messages.BlockChangeMessage; import brainwine.gameserver.server.messages.BlockMetaMessage; @@ -40,6 +47,7 @@ import brainwine.gameserver.server.messages.EntityStatusMessage; import brainwine.gameserver.server.messages.LightMessage; import brainwine.gameserver.server.messages.ZoneExploredMessage; import brainwine.gameserver.server.messages.ZoneStatusMessage; +import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.MathUtils; @JsonIncludeProperties({"name", "biome", "width", "height"}) @@ -128,9 +136,27 @@ public class Zone { } } - public void saveModifiedChunks() { + public void load() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + pendingSunlight.clear(); + File dataDir = new File("zones\\" + documentId); + File shapeFile = new File(dataDir, "shape.cmp"); + BufferUnpacker unpacker = MessagePackHelper.readFiles(shapeFile); + unpacker.read(surface); + unpacker.read(sunlight); + pendingSunlight.addAll(Arrays.asList(unpacker.read(Integer[].class))); + unpacker.read(chunksExplored); + setMetaBlocks(mapper.readerForListOf(MetaBlock.class).readValue(new File(dataDir, "metablocks.json"))); + } + + public void save() throws Exception { + File dataDir = new File("zones\\" + documentId); + ObjectMapper mapper = new ObjectMapper(); + mapper.writerWithDefaultPrettyPrinter().writeValue(new File(dataDir, "config.json"), this); chunkManager.saveModifiedChunks(); removeInactiveChunks(); + MessagePackHelper.writeToFile(new File(dataDir, "shape.cmp"), surface, sunlight, pendingSunlight, chunksExplored); + mapper.writerWithDefaultPrettyPrinter().writeValue(new File(dataDir, "metablocks.json"), metaBlocks.values()); } /** @@ -229,47 +255,54 @@ public class Zone { updateBlock(x, y, Layer.FRONT, 519, 0); } + public void updateBlock(int x, int y, Layer layer, int item) { + updateBlock(x, y, layer, item, 0); + } + public void updateBlock(int x, int y, Layer layer, int item, int mod) { updateBlock(x, y, layer, item, mod, null); } public void updateBlock(int x, int y, Layer layer, int item, int mod, Player owner) { - updateBlock(x, y, layer, ItemRegistry.getItem(item), mod, owner); + updateBlock(x, y, layer, ItemRegistry.getItem(item), mod, owner, new HashMap<>()); + } + + public void updateBlock(int x, int y, Layer layer, int item, int mod, Player owner, Map metadata) { + updateBlock(x, y, layer, ItemRegistry.getItem(item), mod, owner, metadata); + } + + public void updateBlock(int x, int y, Layer layer, Item item) { + updateBlock(x, y, layer, item, 0); } public void updateBlock(int x, int y, Layer layer, Item item, int mod) { updateBlock(x, y, layer, item, mod, null); } - /** - * Updates the block at the specified position, if the coordinates are in bounds. - * Also creates/deletes any metadata & updates sunlight if applicable. - * - * @param x The x position of the block. - * @param y The y position of the block. - * @param layer The layer to modify. - * @param item The item to set it to. - * @param mod The mod to set it to. - * @param owner The owner of the block, can be null. - */ public void updateBlock(int x, int y, Layer layer, Item item, int mod, Player owner) { + updateBlock(x, y, layer, item, mod, owner, new HashMap<>()); + } + + public void updateBlock(int x, int y, Layer layer, Item item, int mod, Player owner, Map metadata) { if(!areCoordinatesInBounds(x, y)) { return; } - Chunk chunk = getChunk(x, y); + Chunk chunk = getChunk(x, y); chunk.getBlock(x, y).updateLayer(layer, item, mod); chunk.setModified(true); sendMessageToChunk(new BlockChangeMessage(x, y, layer, item, mod), chunk); if(layer == Layer.FRONT) { - setMetaBlock(x, y, item, owner); + if(metadata != null && item.hasMeta()) { + setMetaBlock(x, y, item, owner, metadata); + } else if(!item.hasMeta() && metaBlocks.containsKey(getBlockIndex(x, y))) { + setMetaBlock(x, y, 0); + } if(item.isWhole() && y < sunlight[x]) { - // If we place the block higher than the current sunlight, move the sunlight there. sunlight[x] = y; } else if(!item.isWhole() && y == sunlight[x]) { - // If we break the block where the sunlight is at, recalculate from that point. recalculateSunlight(x, sunlight[x]); } @@ -296,96 +329,78 @@ public class Zone { } public void setMetaBlock(int x, int y, int item) { - setMetaBlock(x, y, item, null); + setMetaBlock(x, y, item, null, null); } - public void setMetaBlock(int x, int y, int item, Player owner) { - setMetaBlock(x, y, item, owner, new HashMap()); + public void setMetaBlock(int x, int y, int item, Player owner, Map data) { + setMetaBlock(x, y, ItemRegistry.getItem(item), owner, data); } - public void setMetaBlock(int x, int y, int item, Player owner, Map metadata) { - setMetaBlock(x, y, ItemRegistry.getItem(item), owner, metadata); - } - - public void setMetaBlock(int x, int y, Item item) { - setMetaBlock(x, y, item, null); - } - - public void setMetaBlock(int x, int y, Item item, Player owner) { - setMetaBlock(x, y, item, owner, new HashMap()); - } - - /** - * Sets the metadata of the block at the specified position. - * If the provided item has no meta type, existing metadata will be removed instead. - */ - public void setMetaBlock(int x, int y, Item item, Player owner, Map metadata) { + public void setMetaBlock(int x, int y, Item item, Player owner, Map data) { if(!areCoordinatesInBounds(x, y)) { return; } - Chunk chunk = getChunk(x, y); - int blockIndex = getBlockIndex(x, y); - MetaBlock metaBlock = null; + MetaType meta = item.getMeta(); + int index = getBlockIndex(x, y); + Map metadata = data == null ? new HashMap<>() : MapHelper.copy(data); + Map toSend = MapHelper.copy(metadata); + toSend.put("i", item.getId()); + + if(owner != null) { + toSend.put("p", owner.getDocumentId()); + } if(item.hasMeta()) { - metaBlock = new MetaBlock(x, y, item, metadata); - - if(owner != null) { - metaBlock.setOwner(owner.getDocumentId()); - } - - metaBlocks.put(blockIndex, metaBlock); - - if(item.hasField()) { - fieldBlocks.put(blockIndex, metaBlock); - } - - MetaType meta = item.getMeta(); - - if(meta == MetaType.GLOBAL) { - globalMetaBlocks.put(blockIndex, metaBlock); - sendMessage(new BlockMetaMessage(x, y)); // Landmarks are not properly updated on the client unless they are removed first. - sendMessage(new BlockMetaMessage(metaBlock)); - } else if(meta == MetaType.LOCAL) { - sendMessageToChunk(new BlockMetaMessage(metaBlock), chunk); - } - } else if((metaBlock = metaBlocks.remove(blockIndex)) != null) { - globalMetaBlocks.remove(blockIndex); - fieldBlocks.remove(blockIndex); - MetaType meta = metaBlock.getItem().getMeta(); - - if(meta == MetaType.GLOBAL) { - sendMessage(new BlockMetaMessage(x, y)); - } else if(meta == MetaType.LOCAL) { - sendMessageToChunk(new BlockMetaMessage(x, y), chunk); - } + MetaBlock metaBlock = new MetaBlock(x, y, item, owner, metadata); + metaBlocks.put(index, metaBlock); + indexMetaBlock(index, metaBlock); + } else if(metaBlocks.containsKey(index)) { + meta = metaBlocks.remove(index).getItem().getMeta(); + toSend.clear(); + unindexMetaBlock(index); + } + + switch(meta) { + case LOCAL: + sendMessageToChunk(new BlockMetaMessage(x, y, toSend), getChunk(x, y)); + break; + case GLOBAL: + sendMessage(new BlockMetaMessage(x, y, Collections.emptyMap())); + sendMessage(new BlockMetaMessage(x, y, toSend)); + break; + default: + break; } } - /** - * TODO bad - */ - public void setMetaBlock(int x, int y, MetaBlock metaBlock) { - if(!areCoordinatesInBounds(x, y)) { - return; + private void indexMetaBlock(int index, MetaBlock block) { + Item item = block.getItem(); + metaBlocks.put(index, block); + + if(item.getMeta() == MetaType.GLOBAL) { + globalMetaBlocks.put(index, block); } - int blockIndex = getBlockIndex(x, y); - - if(metaBlock != null) { - metaBlocks.put(blockIndex, metaBlock); + if(item.hasField()) { + fieldBlocks.put(index, block); + } + } + + private void unindexMetaBlock(int index) { + metaBlocks.remove(index); + globalMetaBlocks.remove(index); + fieldBlocks.remove(index); + } + + protected void setMetaBlocks(List metaBlocks) { + for(MetaBlock metaBlock : metaBlocks) { + int x = metaBlock.getX(); + int y = metaBlock.getY(); - if(metaBlock.getItem().getMeta() == MetaType.GLOBAL) { - globalMetaBlocks.put(blockIndex, metaBlock); + if(areCoordinatesInBounds(x, y)) { + indexMetaBlock(getBlockIndex(x, y), metaBlock); } - - if(metaBlock.getItem().hasField()) { - fieldBlocks.put(blockIndex, metaBlock); - } - } else if((metaBlock = metaBlocks.remove(blockIndex)) != null) { - globalMetaBlocks.remove(blockIndex); - fieldBlocks.remove(blockIndex); } } @@ -397,34 +412,27 @@ public class Zone { return player.getDocumentId().equals(metaBlock.getOwner()); } + public MetaBlock getMetaBlock(int x, int y) { + return metaBlocks.get(getBlockIndex(x, y)); + } + + public List getMetaBlocksWithUse(ItemUseType useType){ + return getMetaBlocksWhere(block -> block.getItem().hasUse(useType)); + } + public List getLocalMetaBlocksInChunk(int chunkIndex) { - List metaBlocks = new ArrayList<>(); - - for(MetaBlock metaBlock : getMetaBlocks()) { - if(chunkIndex == getChunkIndex(metaBlock.getX(), metaBlock.getY())) { - if(metaBlock.getItem().getMeta() == MetaType.LOCAL) { - metaBlocks.add(metaBlock); - } - } - } - + return getMetaBlocksWhere(block -> block.getItem().getMeta() == MetaType.LOCAL && chunkIndex == getChunkIndex(block.getX(), block.getY())); + } + + private List getMetaBlocksWhere(Predicate predicate){ + List metaBlocks = new ArrayList<>(this.metaBlocks.values()); + metaBlocks.removeIf(predicate.negate()); return metaBlocks; } public MetaBlock getRandomZoneTeleporter() { - List zoneTeleporters = new ArrayList<>(); - - for(MetaBlock fieldBlock : fieldBlocks.values()) { - if(fieldBlock.getItem().hasUse(ItemUseType.ZONE_TELEPORT)) { - zoneTeleporters.add(fieldBlock); - } - } - - if(zoneTeleporters.isEmpty()) { - return null; - } - - return zoneTeleporters.get((int)(Math.ceil(Math.random()) * (zoneTeleporters.size() - 1))); + List zoneTeleporters = getMetaBlocksWithUse(ItemUseType.ZONE_TELEPORT); + return zoneTeleporters.isEmpty() ? null : zoneTeleporters.get((int)(Math.random() * zoneTeleporters.size())); } public Collection getMetaBlocks() { @@ -538,14 +546,6 @@ public class Zone { return chunks.values(); } - public void setPendingSunlight(int[] sunlight) { - pendingSunlight.clear(); - - for(int i : sunlight) { - pendingSunlight.add(i); - } - } - private void tryRecalculatePendingSunlight(Chunk chunk) { if(!pendingSunlight.isEmpty()) { int chunkX = chunk.getX(); @@ -559,10 +559,6 @@ public class Zone { } } - public Set getPendingSunlight() { - return pendingSunlight; - } - public void setSurface(int x, int surface) { if(x < 0 || x >= width) { return; @@ -571,14 +567,6 @@ public class Zone { this.surface[x] = surface; } - public void setSurface(int[] surface) { - if(surface.length != width) { - return; - } - - System.arraycopy(surface, 0, this.surface, 0, width); - } - public int[] getSurface() { return surface; } @@ -609,14 +597,6 @@ public class Zone { this.sunlight[x] = sunlight; } - public void setSunlight(int[] sunlight) { - if(sunlight.length != width) { - return; - } - - System.arraycopy(sunlight, 0, this.sunlight, 0, width); - } - public int[] getSunlight(int x, int length) { int[] sunlight = new int[length]; @@ -646,14 +626,6 @@ public class Zone { return chunksExplored[chunkIndex] = true; } - public void setChunksExplored(boolean[] chunksExplored) { - if(chunksExplored.length != getChunkCount()) { - return; - } - - System.arraycopy(chunksExplored, 0, this.chunksExplored, 0, chunksExplored.length); - } - /** * @return A float between 0 and 1, where 0 is completely unexplored and 1 is fully explored. */ diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java index a606426..596d698 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java @@ -12,13 +12,10 @@ import java.util.function.Predicate; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.msgpack.unpacker.BufferUnpacker; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.InjectableValues; import com.fasterxml.jackson.databind.ObjectMapper; -import brainwine.gameserver.msgpack.MessagePackHelper; import brainwine.gameserver.zone.gen.AsyncZoneGeneratedHandler; import brainwine.gameserver.zone.gen.AsyncZoneGenerator; import brainwine.gameserver.zone.gen.StaticZoneGenerator; @@ -32,7 +29,6 @@ public class ZoneManager { private Map zonesByName = new HashMap<>(); public ZoneManager() { - dataDir.mkdirs(); loadZones(); asyncGenerator.setDaemon(true); asyncGenerator.start(); @@ -51,27 +47,16 @@ public class ZoneManager { } public void saveZone(Zone zone) { - String id = zone.getDocumentId(); - ObjectMapper mapper = new ObjectMapper(); - File zoneDir = new File(dataDir, id); - zoneDir.mkdirs(); - try { - File configFile = new File(zoneDir, "config.json"); - mapper.writerWithDefaultPrettyPrinter().writeValue(configFile, zone); - File shapeFile = new File(zoneDir, "shape.cmp"); - MessagePackHelper.writeToFile(shapeFile, zone.getSurface(), zone.getSunlight(), zone.getPendingSunlight(), zone.getChunksExplored()); - File metaFile = new File(zoneDir, "metablocks.json"); - mapper.writerWithDefaultPrettyPrinter().writeValue(metaFile, zone.getMetaBlocks()); + zone.save(); } catch(Exception e) { - logger.error("Zone save failure. id: {}", id, e); + logger.error("Zone save failure. id: {}", zone.getDocumentId(), e); } - - zone.saveModifiedChunks(); // TODO } private void loadZones() { logger.info("Loading zone data ..."); + dataDir.mkdirs(); File[] files = dataDir.listFiles(); if(files.length == 0) { @@ -83,7 +68,9 @@ public class ZoneManager { } for(File file : files) { - loadZone(file); + if(file.isDirectory()) { + loadZone(file); + } } logger.info("Successfully loaded {} zone(s)", zonesByName.size()); @@ -99,18 +86,7 @@ public class ZoneManager { try { File configFile = new File(file, "config.json"); Zone zone = mapper.readValue(configFile, Zone.class); - BufferUnpacker unpacker = MessagePackHelper.readFile(new File(file, "shape.cmp")); - zone.setSurface(unpacker.read(int[].class)); - zone.setSunlight(unpacker.read(int[].class)); - zone.setPendingSunlight(unpacker.read(int[].class)); - zone.setChunksExplored(unpacker.read(boolean[].class)); - File metaFile = new File(file, "metablocks.json"); - List metaBlocks = mapper.readValue(metaFile, new TypeReference>(){}); - - for(MetaBlock metaBlock : metaBlocks) { - zone.setMetaBlock(metaBlock.getX(), metaBlock.getY(), metaBlock); - } - + zone.load(); putZone(zone); } catch (Exception e) { logger.error("Zone load failure. id: {}", id, e);