From 86fe6f2138be3d22fab8e9efd2d442d51a5a3d8b Mon Sep 17 00:00:00 2001 From: Kuroppoi Date: Mon, 5 Feb 2024 01:11:38 +0100 Subject: [PATCH 001/176] Add consumables (#32) * Consumable item abstraction & add convert action handler * Add heal & refill action handlers * Pass action details * Add teleport action handler * Notify player if consumable action isn't implemented * Add skill action handler * Convert tabs to spaces * Remove annoying line break * Add skill reset action handler * Init bumped skills map * Better dialog cancellation handling * Add name changer functionality * Add stealth cloak functionality * Remove note * Fix stealth cloak duration * Fix convert consumable crash caused by duplicate item names --- deepworld-config | 2 +- .../main/java/brainwine/gameserver/Timer.java | 38 ++++++++ .../parts/RandomlyTargetBehavior.java | 2 +- .../brainwine/gameserver/dialog/Dialog.java | 27 ++++++ .../dialog/input/DialogSelectInput.java | 13 +++ .../gameserver/entity/player/NameChange.java | 35 +++++++ .../gameserver/entity/player/Player.java | 96 ++++++++++++++++--- .../entity/player/PlayerConfigFile.java | 14 +++ .../entity/player/PlayerManager.java | 13 +++ .../brainwine/gameserver/item/Action.java | 54 ++++++++++- .../java/brainwine/gameserver/item/Item.java | 20 ++++ .../item/consumables/Consumable.java | 9 ++ .../item/consumables/ConvertConsumable.java | 88 +++++++++++++++++ .../item/consumables/HealConsumable.java | 16 ++++ .../consumables/NameChangeConsumable.java | 67 +++++++++++++ .../item/consumables/RefillConsumable.java | 16 ++++ .../item/consumables/SkillConsumable.java | 93 ++++++++++++++++++ .../consumables/SkillResetConsumable.java | 62 ++++++++++++ .../item/consumables/StealthConsumable.java | 26 +++++ .../item/consumables/TeleportConsumable.java | 53 ++++++++++ .../server/requests/DialogRequest.java | 3 +- .../server/requests/InventoryUseRequest.java | 2 +- 22 files changed, 730 insertions(+), 19 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/Timer.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/player/NameChange.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/consumables/Consumable.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/consumables/ConvertConsumable.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/consumables/HealConsumable.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/consumables/NameChangeConsumable.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/consumables/RefillConsumable.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/consumables/SkillConsumable.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/consumables/SkillResetConsumable.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/consumables/StealthConsumable.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/consumables/TeleportConsumable.java diff --git a/deepworld-config b/deepworld-config index ec2749d9..8444603a 160000 --- a/deepworld-config +++ b/deepworld-config @@ -1 +1 @@ -Subproject commit ec2749d94b86dbbdefa52e0146ce278688e28370 +Subproject commit 8444603ae9eb369fc468fb2fb2fe83b040efc4c1 diff --git a/gameserver/src/main/java/brainwine/gameserver/Timer.java b/gameserver/src/main/java/brainwine/gameserver/Timer.java new file mode 100644 index 00000000..fb02f7e7 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/Timer.java @@ -0,0 +1,38 @@ +package brainwine.gameserver; + +/** + * Model for synchronous timers. + */ +public class Timer { + + private T key; + private long time; + private Runnable action; + + public Timer(T key, long delay, Runnable action) { + this.key = key; + this.time = System.currentTimeMillis() + delay; + this.action = action; + } + + public boolean process() { + if(System.currentTimeMillis() >= time) { + action.run(); + return true; + } + + return false; + } + + public T getKey() { + return key; + } + + public long getTime() { + return time; + } + + public Runnable getAction() { + return action; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/RandomlyTargetBehavior.java b/gameserver/src/main/java/brainwine/gameserver/behavior/parts/RandomlyTargetBehavior.java index 3311393d..afc982a6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/RandomlyTargetBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/behavior/parts/RandomlyTargetBehavior.java @@ -31,7 +31,7 @@ public boolean behave() { if(!entity.hasTarget()) { Player target = entity.getZone().getRandomPlayerInRange(entity.getX(), entity.getY(), range); - if(target != null && !target.isGodMode() && !target.isDead() && !entity.isOwnedBy(target) && (!blockable || entity.canSee(target))) { + if(target != null && !target.isGodMode() && !target.isStealthy() && !target.isDead() && !entity.isOwnedBy(target) && (!blockable || entity.canSee(target))) { entity.setTarget(target); targetLockedAt = now; } diff --git a/gameserver/src/main/java/brainwine/gameserver/dialog/Dialog.java b/gameserver/src/main/java/brainwine/gameserver/dialog/Dialog.java index d931eed2..cd2690c0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/dialog/Dialog.java +++ b/gameserver/src/main/java/brainwine/gameserver/dialog/Dialog.java @@ -1,12 +1,15 @@ package brainwine.gameserver.dialog; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; 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.JsonSetter; @JsonInclude(Include.NON_DEFAULT) @JsonIgnoreProperties(ignoreUnknown = true) @@ -15,6 +18,7 @@ public class Dialog { private DialogType type = DialogType.STANDARD; private DialogAlignment alignment = DialogAlignment.LEFT; private List sections = new ArrayList<>(); + private Object actions; private String title; private String target; @@ -61,6 +65,29 @@ public List getSections() { return sections; } + @JsonSetter + private void setActions(Object actions) { + this.actions = actions; + } + + public Dialog setActions(String actions) { + this.actions = actions; + return this; + } + + public Dialog setActions(String... actions) { + return setActions(Arrays.asList(actions)); + } + + public Dialog setActions(Collection actions) { + this.actions = actions; + return this; + } + + public Object getActions() { + return actions; + } + public Dialog setTitle(String title) { this.title = title; return this; diff --git a/gameserver/src/main/java/brainwine/gameserver/dialog/input/DialogSelectInput.java b/gameserver/src/main/java/brainwine/gameserver/dialog/input/DialogSelectInput.java index cffab2c4..39ce7672 100644 --- a/gameserver/src/main/java/brainwine/gameserver/dialog/input/DialogSelectInput.java +++ b/gameserver/src/main/java/brainwine/gameserver/dialog/input/DialogSelectInput.java @@ -3,9 +3,12 @@ import java.util.Arrays; import java.util.Collection; +import com.fasterxml.jackson.annotation.JsonProperty; + public class DialogSelectInput extends DialogInput { private Collection options; + private int maxColumns; public DialogSelectInput setOptions(String... options) { return setOptions(Arrays.asList(options)); @@ -19,4 +22,14 @@ public DialogSelectInput setOptions(Collection options) { public Collection getOptions() { return options; } + + public DialogSelectInput setMaxColumns(int maxColumns) { + this.maxColumns = maxColumns; + return this; + } + + @JsonProperty("max columns") + public int getMaxColumns() { + return maxColumns; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/NameChange.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/NameChange.java new file mode 100644 index 00000000..5252cdad --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/NameChange.java @@ -0,0 +1,35 @@ +package brainwine.gameserver.entity.player; + +import java.time.OffsetDateTime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; + +@JsonIncludeProperties({"new_name", "previous_name", "date"}) +public class NameChange { + + private String newName; + private String previousName; + private OffsetDateTime date; + + @JsonCreator + private NameChange() {} + + public NameChange(String newName, String previousName) { + this.newName = newName; + this.previousName = previousName; + date = OffsetDateTime.now(); + } + + public String getNewName() { + return newName; + } + + public String getPreviousName() { + return previousName; + } + + public OffsetDateTime getDate() { + return date; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java index dd55321a..7fc29a75 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java @@ -19,6 +19,7 @@ import brainwine.gameserver.GameConfiguration; import brainwine.gameserver.GameServer; +import brainwine.gameserver.Timer; import brainwine.gameserver.achievements.Achievement; import brainwine.gameserver.achievements.AchievementManager; import brainwine.gameserver.achievements.JourneymanAchievement; @@ -29,14 +30,13 @@ import brainwine.gameserver.dialog.DialogType; import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.EntityStatus; -import brainwine.gameserver.entity.FacingDirection; import brainwine.gameserver.entity.npc.Npc; -import brainwine.gameserver.item.Action; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; import brainwine.gameserver.item.MiningBonus; +import brainwine.gameserver.item.consumables.Consumable; import brainwine.gameserver.loot.Loot; import brainwine.gameserver.server.Message; import brainwine.gameserver.server.messages.AchievementMessage; @@ -95,16 +95,19 @@ public class Player extends Entity implements CommandExecutor { private Inventory inventory; private PlayerStatistics statistics; private List authTokens; + private List nameChanges; private List mutes; private List bans; private Set achievements; private Map ignoredHints; private Map skills; + private Map> bumpedSkills; private Map equippedClothing; private Map equippedColors; private final Map settings = new HashMap<>(); private final Set activeChunks = new HashSet<>(); private final Map> dialogs = new HashMap<>(); + private final List> timers = new ArrayList<>(); private final List trackedEntities = new ArrayList<>(); private String clientVersion; private Placement lastPlacement; @@ -112,6 +115,7 @@ public class Player extends Entity implements CommandExecutor { private Vector2i spawnPoint = new Vector2i(0, 0); private int teleportX; private int teleportY; + private boolean stealth; private boolean godMode; private long lastHeartbeat; private long lastTrackedEntityUpdate; @@ -132,11 +136,13 @@ protected Player(String documentId, PlayerConfigFile config) { this.inventory = config.getInventory(); this.statistics = config.getStatistics(); this.authTokens = config.getAuthTokens(); + this.nameChanges = config.getNameChanges(); this.mutes = config.getMutes(); this.bans = config.getBans(); this.achievements = config.getAchievements(); this.ignoredHints = config.getIgnoredHints(); this.skills = config.getSkills(); + this.bumpedSkills = config.getBumpedSkills(); this.equippedClothing = config.getEquippedClothing(); this.equippedColors = config.getEquippedColors(); health = getMaxHealth(); @@ -151,11 +157,13 @@ public Player(String documentId, String name, Zone zone) { this.inventory = new Inventory(this); this.statistics = new PlayerStatistics(this); this.authTokens = new ArrayList<>(); + this.nameChanges = new ArrayList<>(); this.mutes = new ArrayList<>(); this.bans = new ArrayList<>(); this.achievements = new HashSet<>(); this.ignoredHints = new HashMap<>(); this.skills = new HashMap<>(); + this.bumpedSkills = new HashMap<>(); this.equippedClothing = new HashMap<>(); this.equippedColors = new HashMap<>(); } @@ -182,6 +190,9 @@ public void tick(float deltaTime) { heal(BASE_REGEN_AMOUNT * deltaTime); } + // Process timers + timers.removeIf(Timer::process); + // Update tracked entities if(now - lastTrackedEntityUpdate >= TRACKED_ENTITY_UPDATE_INTERVAL) { updateTrackedEntities(); @@ -223,6 +234,15 @@ public void setHealth(float health) { sendMessage(new HealthMessage(health)); } + @Override + public void setProperties(Map properties, boolean sendMessage) { + super.setProperties(properties, sendMessage); + + if(sendMessage) { + sendMessage(new EntityChangeMessage(id, properties)); + } + } + /** * @return A {@link Map} containing all the data necessary for use in {@link EntityStatusMessage}. */ @@ -322,6 +342,8 @@ public void onZoneChanged() { /** * Called from {@link Connection} when the channel becomes inactive. + * + * TODO Should we force process all timers on disconnect? */ public void onDisconnect() { lastHeartbeat = 0; @@ -413,20 +435,31 @@ public void showDialog(Dialog dialog, Consumer handler) { } public void handleDialogInput(int id, Object[] input) { - if(id == 0 || (input.length == 1 && input[0].equals("cancel"))) { + if(id == 0) { return; } Consumer handler = dialogs.remove(id); if(handler == null) { - notify("Sorry, the request has expired."); + if(!(input.length == 1 && input[0].equals("cancel"))) { + notify("Sorry, the request has expired."); + } } else { // TODO since we're dealing with user input, should we just try-catch this? handler.accept(input); } } + public void addTimer(String key, long delay, Runnable action) { + removeTimer(key); + timers.add(new Timer<>(key, delay, action)); + } + + public void removeTimer(String key) { + timers.removeIf(timer -> timer.getKey().equals(key)); + } + public void checkRegistration() { if(!isRegistered()) { sendMessage(new EventMessage("playerRegistered", false)); @@ -502,6 +535,15 @@ public int getTeleportY() { return teleportY; } + public void setStealth(boolean stealth) { + this.stealth = stealth; + setProperty("xs", stealth ? 1 : 0, true); + } + + public boolean isStealthy() { + return stealth; + } + public void setGodMode(boolean godMode) { this.godMode = godMode; } @@ -657,6 +699,14 @@ protected List getAuthTokens() { return authTokens; } + public void trackNameChange(String newName) { + nameChanges.add(new NameChange(newName, name)); + } + + public List getNameChanges() { + return nameChanges; + } + public void mute(String reason, OffsetDateTime until) { mute(null, reason, until); } @@ -984,19 +1034,39 @@ public Map getSkills() { return Collections.unmodifiableMap(skills); } - public void consume(Item item) { - Action action = item.getAction(); + public void trackSkillBump(Item item, Skill skill) { + List skills = bumpedSkills.get(item); - // TODO some kind of abstraction for things like this would be pretty cool - switch(action) { - case HEAL: heal(item.getPower()); break; - default: break; + if(skills == null) { + skills = new ArrayList<>(); + bumpedSkills.put(item, skills); } - // (Temporary?) measure to prevent consuming unimplemented consumables - if(action != Action.NONE) { - inventory.removeItem(item); + skills.add(skill); + } + + public boolean hasSkillBeenBumped(Item item, Skill skill) { + return bumpedSkills.getOrDefault(item, Collections.emptyList()).contains(skill); + } + + public Map> getBumpedSkills() { + return bumpedSkills; + } + + public void consume(Item item) { + consume(item, null); + } + + public void consume(Item item, Object details) { + Consumable consumable = item.getAction().getConsumable(); + + if(consumable == null) { + sendMessage(new InventoryMessage(inventory.getClientConfig(item))); + notify("Sorry, this action hasn't been implemented yet."); + return; } + + consumable.consume(item, this, details); } public void awardLoot(Loot loot) { diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerConfigFile.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerConfigFile.java index 623a3216..76c64430 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerConfigFile.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerConfigFile.java @@ -31,11 +31,13 @@ public class PlayerConfigFile { private Inventory inventory = new Inventory(); private PlayerStatistics statistics = new PlayerStatistics(); private List authTokens = new ArrayList<>(); + private List nameChanges = new ArrayList<>(); private List mutes = new ArrayList<>(); private List bans = new ArrayList<>(); private Set achievements = new HashSet<>(); private Map ignoredHints = new HashMap<>(); private Map skills = new HashMap<>(); + private Map> bumpedSkills = new HashMap<>(); private Map equippedClothing = new HashMap<>(); private Map equippedColors = new HashMap<>(); @@ -52,11 +54,13 @@ public PlayerConfigFile(Player player) { this.inventory = player.getInventory(); this.statistics = player.getStatistics(); this.authTokens = player.getAuthTokens(); + this.nameChanges = player.getNameChanges(); this.mutes = player.getMutes(); this.bans = player.getBans(); this.achievements = player.getAchievements(); this.ignoredHints = player.getIgnoredHints(); this.skills = player.getSkills(); + this.bumpedSkills = player.getBumpedSkills(); this.equippedClothing = player.getEquippedClothing(); this.equippedColors = player.getEquippedColors(); } @@ -90,6 +94,11 @@ public List getAuthTokens() { return authTokens; } + @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) + public List getNameChanges() { + return nameChanges; + } + @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) public List getMutes() { return mutes; @@ -141,6 +150,11 @@ public Map getSkills() { return skills; } + @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) + public Map> getBumpedSkills() { + return bumpedSkills; + } + @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) public Map getEquippedClothing() { return equippedClothing; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerManager.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerManager.java index b6a2f940..463ed3f5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerManager.java @@ -139,6 +139,19 @@ public boolean verifyAuthToken(String name, String authToken) { return false; } + public void changePlayerName(Player player, String name) { + if(playersByName.containsKey(name)) { + logger.warn("Tried to rename player {} to already existing name {}", player.getDocumentId(), name); + return; + } + + // Track name change and re-index the player + playersByName.remove(player.getName().toLowerCase()); + player.trackNameChange(name); + player.setName(name); + playersByName.put(name.toLowerCase(), player); + } + public void onPlayerConnect(Player player) { Connection connection = player.getConnection(); playersByConnection.put(connection, player); diff --git a/gameserver/src/main/java/brainwine/gameserver/item/Action.java b/gameserver/src/main/java/brainwine/gameserver/item/Action.java index 960392c1..6c2acf6b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/Action.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/Action.java @@ -1,13 +1,63 @@ package brainwine.gameserver.item; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; +import brainwine.gameserver.item.consumables.Consumable; +import brainwine.gameserver.item.consumables.ConvertConsumable; +import brainwine.gameserver.item.consumables.HealConsumable; +import brainwine.gameserver.item.consumables.NameChangeConsumable; +import brainwine.gameserver.item.consumables.RefillConsumable; +import brainwine.gameserver.item.consumables.SkillConsumable; +import brainwine.gameserver.item.consumables.SkillResetConsumable; +import brainwine.gameserver.item.consumables.StealthConsumable; +import brainwine.gameserver.item.consumables.TeleportConsumable; + +/** + * Action types for items. + * + * All consumables depend on their action type, but not all items with actions are consumables. + * This creates a bit of an awkward situation in terms of implementation, but we're just gonna have to deal with that. + */ public enum Action { + CONVERT(new ConvertConsumable()), DIG, - HEAL, - REFILL, + HEAL(new HealConsumable()), + NAME_CHANGE(new NameChangeConsumable()), + REFILL(new RefillConsumable()), + SKILL(new SkillConsumable()), + SKILL_RESET(new SkillResetConsumable()), + STEALTH(new StealthConsumable()), + TELEPORT(new TeleportConsumable()), @JsonEnumDefaultValue NONE; + + private final Consumable consumable; + + private Action(Consumable consumable) { + this.consumable = consumable; + } + + private Action() { + this(null); + } + + @JsonCreator + public static Action fromId(String id) { + String formatted = id.toUpperCase().replace(" ", "_"); + + for(Action value : values()) { + if(value.toString().equals(formatted)) { + return value; + } + } + + return NONE; + } + + public Consumable getConsumable() { + return consumable; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/item/Item.java b/gameserver/src/main/java/brainwine/gameserver/item/Item.java index fa2e36cc..fb00377c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/Item.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/Item.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -16,6 +17,7 @@ import brainwine.gameserver.util.Pair; import brainwine.gameserver.util.Vector2i; +// TODO I don't like some parts of this, maybe they can be reworked. @JsonIgnoreProperties(ignoreUnknown = true) public class Item { @@ -132,6 +134,9 @@ public class Item { @JsonProperty("skill_bonuses") private Map skillBonuses = new HashMap<>(); + @JsonProperty("power_bonus") + private Pair powerBonus; + @JsonProperty("mining skill") private Pair miningSkill; @@ -153,6 +158,9 @@ public class Item { @JsonProperty("use") private Map useConfigs = new HashMap<>(); + @JsonProperty("convert") + private Map conversions = new HashMap<>(); + @JsonCreator private Item(@JsonProperty(value = "id", required = true) String id, @JsonProperty(value = "code", required = true) int code) { @@ -359,6 +367,14 @@ public Map getSkillBonuses() { return skillBonuses; } + public boolean hasPowerBonus() { + return powerBonus != null; + } + + public Pair getPowerBonus() { + return powerBonus; + } + public boolean requiresMiningSkill() { return miningSkill != null; } @@ -456,4 +472,8 @@ public Object getUse(ItemUseType type) { public Map getUses() { return useConfigs; } + + public Map getConversions() { + return conversions.entrySet().stream().collect(Collectors.toMap(entry -> entry.getKey().get(), entry -> entry.getValue().get())); + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/Consumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/Consumable.java new file mode 100644 index 00000000..32b3a77a --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/Consumable.java @@ -0,0 +1,9 @@ +package brainwine.gameserver.item.consumables; + +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; + +public interface Consumable { + + public void consume(Item item, Player player, Object details); +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/ConvertConsumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/ConvertConsumable.java new file mode 100644 index 00000000..8bf729ce --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/ConvertConsumable.java @@ -0,0 +1,88 @@ +package brainwine.gameserver.item.consumables; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import brainwine.gameserver.dialog.Dialog; +import brainwine.gameserver.dialog.DialogSection; +import brainwine.gameserver.dialog.input.DialogSelectInput; +import brainwine.gameserver.entity.player.Inventory; +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.ItemRegistry; +import brainwine.gameserver.server.messages.InventoryMessage; + +/** + * Consumable handler for upgrade kits + */ +public class ConvertConsumable implements Consumable { + + @Override + public void consume(Item item, Player player, Object details) { + Map conversions = item.getConversions(); + Inventory inventory = player.getInventory(); + + // Find items in the player's inventory that can be upgraded + Set convertables = conversions.keySet().stream().filter(i -> inventory.hasItem(i)).collect(Collectors.toSet()); + + // Don't do anything if the player has no items that can be converted + if(convertables.isEmpty()) { + player.notify("You do not have any upgradeable items."); + player.sendMessage(new InventoryMessage(inventory.getClientConfig(item))); + return; + } + + // Map item titles to their id + Map keyMap = convertables.stream().collect(Collectors.toMap(Item::getTitle, Item::getId, (a, b) -> a)); + + // Create upgrade dialog + Dialog dialog = new Dialog().addSection(new DialogSection() + .setTitle("Which item would you like to upgrade?") + .setInput(new DialogSelectInput() + .setOptions(convertables.stream().map(Item::getTitle).collect(Collectors.toList())) + .setMaxColumns(3) + .setKey("item"))); + + player.showDialog(dialog, data -> { + // Handle cancellation + if(data.length == 1 && data[0].equals("cancel")) { + player.sendMessage(new InventoryMessage(player.getInventory().getClientConfig(item))); + return; + } + + // Fail if there is no data + if(data.length == 0) { + fail(item, player); + return; + } + + String key = keyMap.get(data[0]); + + // Fail if the chosen item title doesn't map to an id + if(key == null) { + fail(item, player); + return; + } + + Item itemToUpgrade = ItemRegistry.getItem(key); + Item targetItem = conversions.get(itemToUpgrade); + + // Fail if the player doesn't have the item they want to upgrade or there is no upgrade for it + if(!inventory.hasItem(itemToUpgrade) || targetItem == null) { + fail(item, player); + return; + } + + inventory.removeItem(item, true); // Remove the consumable + inventory.removeItem(itemToUpgrade, true); // Remove the item that was upgraded + inventory.addItem(targetItem, true); // Add the item that the item upgraded to :) + player.notify(String.format("%s upgraded to %s!", itemToUpgrade.getTitle(), targetItem.getTitle())); + }); + } + + private void fail(Item item, Player player) { + player.notify("Oops! There was a problem with the upgrade."); + player.sendMessage(new InventoryMessage(player.getInventory().getClientConfig(item))); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/HealConsumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/HealConsumable.java new file mode 100644 index 00000000..5473f5b1 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/HealConsumable.java @@ -0,0 +1,16 @@ +package brainwine.gameserver.item.consumables; + +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; + +/** + * Consumable handler for healing items + */ +public class HealConsumable implements Consumable { + + @Override + public void consume(Item item, Player player, Object details) { + player.heal(item.getPower()); + player.getInventory().removeItem(item); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/NameChangeConsumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/NameChangeConsumable.java new file mode 100644 index 00000000..90b3a1f0 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/NameChangeConsumable.java @@ -0,0 +1,67 @@ +package brainwine.gameserver.item.consumables; + +import java.util.regex.Pattern; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.dialog.Dialog; +import brainwine.gameserver.dialog.DialogHelper; +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.entity.player.PlayerManager; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.server.messages.EventMessage; +import brainwine.gameserver.server.messages.InventoryMessage; + +/** + * Consumable handler for name changers + */ +public class NameChangeConsumable implements Consumable { + + private static final Pattern namePattern = Pattern.compile("^[a-zA-Z0-9_.-]{4,20}$"); + + @Override + public void consume(Item item, Player player, Object details) { + PlayerManager playerManager = GameServer.getInstance().getPlayerManager(); + Dialog dialog = DialogHelper.inputDialog("Change your name", + "Your in-game name can include letters, numbers, dashes and periods, and must be between 4 and 20 characters in length."); + + player.showDialog(dialog, data -> { + // Handle cancellation + if(data.length == 1 && data[0].equals("cancel")) { + player.sendMessage(new InventoryMessage(player.getInventory().getClientConfig(item))); + return; + } + + String name = data.length == 1 ? "" + data[0] : null; + + // Check if the data is present + if(name == null) { + fail(item, player, "Oops! There was a problem with your request."); + return; + } + + // Check if the name is valid + if(!namePattern.matcher(name).matches()) { + fail(item, player, "Please enter a valid name."); + return; + } + + // Check if name is already taken + if(playerManager.getPlayer(name) != null) { + fail(item, player, "That name is taken already."); + return; + } + + player.getInventory().removeItem(item); // Remove the consumable + playerManager.changePlayerName(player, name); // Process the name change + + // TODO this creates a race condition + player.sendMessage(new EventMessage("playerNameDidChange", name)); // Client side processing stuff + player.kick("Your name has been changed."); // Force the player to reconnect + }); + } + + private void fail(Item item, Player player, String message) { + player.showDialog(DialogHelper.messageDialog(message)); + player.sendMessage(new InventoryMessage(player.getInventory().getClientConfig(item))); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/RefillConsumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/RefillConsumable.java new file mode 100644 index 00000000..a965a13d --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/RefillConsumable.java @@ -0,0 +1,16 @@ +package brainwine.gameserver.item.consumables; + +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; + +/** + * Consumable handler for steam canisters + */ +public class RefillConsumable implements Consumable { + + @Override + public void consume(Item item, Player player, Object details) { + // All we do is remove the item because steam functionality is pretty much entirely client-side + player.getInventory().removeItem(item); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/SkillConsumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/SkillConsumable.java new file mode 100644 index 00000000..d62a6610 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/SkillConsumable.java @@ -0,0 +1,93 @@ +package brainwine.gameserver.item.consumables; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.commons.text.WordUtils; + +import brainwine.gameserver.dialog.Dialog; +import brainwine.gameserver.dialog.DialogHelper; +import brainwine.gameserver.dialog.DialogSection; +import brainwine.gameserver.dialog.input.DialogSelectInput; +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.entity.player.Skill; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.server.messages.InventoryMessage; + +/** + * Consumable handler for skill upgrade items + */ +public class SkillConsumable implements Consumable { + + @Override + public void consume(Item item, Player player, Object details) { + List bumpedSkills = player.getBumpedSkills().getOrDefault(item, Collections.emptyList()); + + // Check if all skills have been bumped already + if(bumpedSkills.size() >= Skill.values().length) { + player.notify(String.format("You have already increased all of your skills with %ss.", item.getTitle().toLowerCase())); + player.sendMessage(new InventoryMessage(player.getInventory().getClientConfig(item))); + return; + } + + // Assemble a list of skills that can be upgraded with this consumable + List upgradeableSkills = Arrays.asList(Skill.values()).stream() + .filter(skill -> !bumpedSkills.contains(skill) && player.getSkillLevel(skill) < 10) + .collect(Collectors.toList()); + + // Check if there are any skills to upgrade + if(upgradeableSkills.isEmpty()) { + player.notify("You have maximized all skills available for mastery."); + player.sendMessage(new InventoryMessage(player.getInventory().getClientConfig(item))); + return; + } + + List upgradeableSkillNames = upgradeableSkills.stream() + .map(Skill::getId) + .map(WordUtils::capitalize) + .collect(Collectors.toList()); + + // Create dialog + Dialog dialog = new Dialog().addSection(new DialogSection() + .setTitle("Which skill would you like to increase?") + .setInput(new DialogSelectInput() + .setOptions(upgradeableSkillNames) + .setMaxColumns(3) + .setKey("skill"))); + + player.showDialog(dialog, data -> { + // Handle cancellation + if(data.length == 1 && data[0].equals("cancel")) { + player.sendMessage(new InventoryMessage(player.getInventory().getClientConfig(item))); + return; + } + + // Verify data + if(data.length != 1) { + fail(item, player); + return; + } + + Skill skill = Skill.fromId("" + data[0]); + + // Make sure that the skill is still eligible for upgrading + if(skill == null || player.hasSkillBeenBumped(item, skill) || player.getSkillLevel(skill) >= 10) { + fail(item, player); + return; + } + + player.getInventory().removeItem(item, true); // Remove consumable + player.trackSkillBump(item, skill); // Track skill bump + player.setSkillLevel(skill, player.getSkillLevel(skill) + 1); // Increase skill level + player.showDialog(DialogHelper.messageDialog(String.format("%s increased!", WordUtils.capitalize(skill.toString().toLowerCase())), + String.format("You now have additional mastery of %s.", skill.toString().toLowerCase()))); + }); + } + + private void fail(Item item, Player player) { + player.notify("Oops! There was a problem with upgrading your skill."); + player.sendMessage(new InventoryMessage(player.getInventory().getClientConfig(item))); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/SkillResetConsumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/SkillResetConsumable.java new file mode 100644 index 00000000..38205668 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/SkillResetConsumable.java @@ -0,0 +1,62 @@ +package brainwine.gameserver.item.consumables; + +import java.util.Map.Entry; + +import brainwine.gameserver.dialog.Dialog; +import brainwine.gameserver.dialog.DialogHelper; +import brainwine.gameserver.dialog.DialogSection; +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.entity.player.Skill; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.server.messages.InventoryMessage; + +/** + * Consumable handler for skill resets + */ +public class SkillResetConsumable implements Consumable { + + @Override + public void consume(Item item, Player player, Object details) { + // Create dialog + Dialog dialog = new Dialog() + .setActions("yesno") + .addSection(new DialogSection() + .setTitle("Confirm skill reset") + .setText("Are you sure that you want to reset all of your skills back to level 1?")); + + player.showDialog(dialog, data -> { + // Handle cancellation + if(data.length == 1 && data[0].equals("cancel")) { + player.sendMessage(new InventoryMessage(player.getInventory().getClientConfig(item))); + return; + } + + // Check if there are any skills to reset + if(!player.getSkills().values().stream().anyMatch(level -> level > 1)) { + player.showDialog(DialogHelper.messageDialog("You don't have any skills to reset.")); + player.sendMessage(new InventoryMessage(player.getInventory().getClientConfig(item))); + return; + } + + int pointsToRefund = 0; + + // Reset skill levels and calculate point refund total + for(Entry entry : player.getSkills().entrySet()) { + Skill skill = entry.getKey(); + int level = entry.getValue(); + + // Skip if skill hasn't been upgraded at all + if(level <= 1) { + continue; + } + + pointsToRefund += level - 1; + player.setSkillLevel(skill, 1); // Reset skill level + } + + player.getInventory().removeItem(item, true); // Remove the consumable + player.setSkillPoints(player.getSkillPoints() + pointsToRefund); // Refund skill points + player.showDialog(DialogHelper.getDialog("skill_reset")); + }); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/StealthConsumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/StealthConsumable.java new file mode 100644 index 00000000..562b49e9 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/StealthConsumable.java @@ -0,0 +1,26 @@ +package brainwine.gameserver.item.consumables; + +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; + +/** + * Consumable handler for stealth cloaks + */ +public class StealthConsumable implements Consumable { + + @Override + public void consume(Item item, Player player, Object details) { + player.getInventory().removeItem(item); + player.setStealth(true); + float seconds = item.getPower(); + + // Apply skill power bonus + if(item.hasPowerBonus()) { + seconds += player.getTotalSkillLevel(item.getPowerBonus().getFirst()) * item.getPowerBonus().getLast(); + } + + // Create timer + long delay = (long)(seconds * 1000); + player.addTimer("end stealth", delay, () -> player.setStealth(false)); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/TeleportConsumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/TeleportConsumable.java new file mode 100644 index 00000000..fa6138e0 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/TeleportConsumable.java @@ -0,0 +1,53 @@ +package brainwine.gameserver.item.consumables; + +import java.util.List; + +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.ItemUseType; +import brainwine.gameserver.server.messages.InventoryMessage; +import brainwine.gameserver.zone.MetaBlock; + +/** + * Consumable handler for portable teleporters. + * + * These do not seem to function correctly on v3 clients. + */ +public class TeleportConsumable implements Consumable { + + @Override + public void consume(Item item, Player player, Object details) { + // Verify details + if(details == null || !(details instanceof List)) { + fail(item, player); + return; + } + + @SuppressWarnings("unchecked") + List coordinates = (List)details; + + // Verify coordinates + if(coordinates.size() != 2 || !(coordinates.get(0) instanceof Integer) || !(coordinates.get(1) instanceof Integer)) { + fail(item, player); + return; + } + + int x = (int)coordinates.get(0); + int y = (int)coordinates.get(1); + MetaBlock block = player.getZone().getMetaBlock(x, y); + + // Check if there is a teleporter at the target location + if(block == null || !block.getItem().hasUse(ItemUseType.TELEPORT, ItemUseType.ZONE_TELEPORT)) { + fail(item, player); + return; + } + + player.getInventory().removeItem(item); + player.teleport(x, y); + } + + private void fail(Item item, Player player) { + player.notify("Oops! There was a problem teleporting you to your target destination."); + player.sendMessage(new InventoryMessage(player.getInventory().getClientConfig(item))); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java index 44e1b218..e40904c2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java @@ -73,10 +73,11 @@ private void onSkillUpgrade(Player player) { "Note: Additional skills like Combat and Engineering are unlocked as you progress." : null) .setInput(new DialogSelectInput() .setOptions(upgradeableSkillNames) + .setMaxColumns(3) .setKey("skill"))); player.showDialog(dialog, input -> { - if(input.length == 0) { + if(input.length == 0 || input[0].equals("cancel")) { return; } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java index 7847ebcc..98a6d7a2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java @@ -36,7 +36,7 @@ public void process(Player player) { // Try to consume item if it is a consumable if(item.isConsumable()) { if(status == 1) { - player.consume(item); + player.consume(item, details); } } else { // Set current held item if applicable From 8a9bc80c491328387cf651e81e27ebe02a62836e Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Mon, 5 Feb 2024 01:13:52 +0100 Subject: [PATCH 002/176] Send skill data before inventory data --- .../java/brainwine/gameserver/entity/player/Player.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java index 7fc29a75..0c2221a2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java @@ -296,15 +296,16 @@ public void onZoneChanged() { sendMessage(new ZoneStatusMessage(zone.getStatusConfig())); sendMessage(new PlayerPositionMessage((int)x, (int)y)); sendMessage(new HealthMessage(health)); - sendMessage(new InventoryMessage(inventory)); - sendMessage(new WardrobeMessage(inventory.getWardrobe())); - sendMessage(new BlockMetaMessage(zone.getGlobalMetaBlocks())); // Send skill data for(Skill skill : skills.keySet()) { sendMessage(new SkillMessage(skill, skills.get(skill))); } + sendMessage(new InventoryMessage(inventory)); + sendMessage(new WardrobeMessage(inventory.getWardrobe())); + sendMessage(new BlockMetaMessage(zone.getGlobalMetaBlocks())); + // Send peer data Collection peers = zone.getPlayers(); sendMessage(new EntityStatusMessage(peers, EntityStatus.ENTERING)); From 81ceaa45427ec68d22c180981b80e74c2a566230 Mon Sep 17 00:00:00 2001 From: Kuroppoi Date: Sat, 17 Feb 2024 21:57:36 +0100 Subject: [PATCH 003/176] Implement a bunch of block interactions (#34) * Block interaction abstraction, block timers & timed switch functionality Also fixes #31 :) * Add chest o' plenty functionality * Add spawn teleporter functionality * Add transmitter functionality * Ignore transmit limit if the player has god mode enabled * Oops, gotta use this one * Bomb explosions test * Explosion raycasting * Oops forgot this * Add proximity burst & spawn functionality * Forgot to switch this back * Add player stealth check for mines * Touchplates, spawners, exploders, and other goodies * Diamond & onyx target teleporter functionality * Fireplace stuff * Check for entity spawns on block mine * Spawn bomb functionality * Liquid bomb functionality --- deepworld-config | 2 +- .../main/java/brainwine/gameserver/Timer.java | 6 +- .../brainwine/gameserver/entity/Entity.java | 49 +++- .../brainwine/gameserver/entity/npc/Npc.java | 18 +- .../gameserver/entity/player/Player.java | 101 ++++++-- .../entity/player/PlayerConfigFile.java | 7 + .../java/brainwine/gameserver/item/Item.java | 55 +++++ .../gameserver/item/ItemUseType.java | 52 ++++- .../item/interactions/BurstInteraction.java | 63 +++++ .../item/interactions/ChangeInteraction.java | 26 +++ .../interactions/ContainerInteraction.java | 88 +++++++ .../item/interactions/DialogInteraction.java | 98 ++++++++ .../item/interactions/ItemInteraction.java | 13 ++ .../item/interactions/SpawnInteraction.java | 39 ++++ .../SpawnTeleportInteraction.java | 39 ++++ .../item/interactions/SwitchInteraction.java | 215 ++++++++++++++++++ .../TargetTeleportInteraction.java | 102 +++++++++ .../interactions/TeleportInteraction.java | 56 +++++ .../interactions/TransmitInteraction.java | 58 +++++ .../java/brainwine/gameserver/loot/Loot.java | 7 + .../server/messages/EffectMessage.java | 6 +- .../server/requests/BlockMineRequest.java | 48 ++++ .../server/requests/BlockPlaceRequest.java | 117 +++++++++- .../server/requests/BlockUseRequest.java | 163 ++----------- .../brainwine/gameserver/util/MathUtils.java | 6 +- .../java/brainwine/gameserver/zone/Block.java | 64 +++--- .../brainwine/gameserver/zone/MetaBlock.java | 58 +++++ .../java/brainwine/gameserver/zone/Zone.java | 194 ++++++++++++++-- .../gameserver/zone/ZoneManager.java | 7 +- 29 files changed, 1528 insertions(+), 229 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/BurstInteraction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/ChangeInteraction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/ItemInteraction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnInteraction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnTeleportInteraction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/TargetTeleportInteraction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/TeleportInteraction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/TransmitInteraction.java diff --git a/deepworld-config b/deepworld-config index 8444603a..827c1fac 160000 --- a/deepworld-config +++ b/deepworld-config @@ -1 +1 @@ -Subproject commit 8444603ae9eb369fc468fb2fb2fe83b040efc4c1 +Subproject commit 827c1fac075ed2a9d92f56696f36101d54485109 diff --git a/gameserver/src/main/java/brainwine/gameserver/Timer.java b/gameserver/src/main/java/brainwine/gameserver/Timer.java index fb02f7e7..7c817fc5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/Timer.java +++ b/gameserver/src/main/java/brainwine/gameserver/Timer.java @@ -16,7 +16,11 @@ public Timer(T key, long delay, Runnable action) { } public boolean process() { - if(System.currentTimeMillis() >= time) { + return process(false); + } + + public boolean process(boolean force) { + if(force || System.currentTimeMillis() >= time) { action.run(); return true; } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java index 202e395b..6009cc9f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java @@ -6,11 +6,16 @@ import java.util.Map; import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.ItemUseType; +import brainwine.gameserver.item.Layer; import brainwine.gameserver.server.Message; import brainwine.gameserver.server.messages.EntityChangeMessage; import brainwine.gameserver.server.messages.EntityStatusMessage; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.MathUtils; +import brainwine.gameserver.zone.Block; +import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; public abstract class Entity { @@ -29,6 +34,10 @@ public abstract class Entity { protected float y; protected float velocityX; protected float velocityY; + protected int blockX; + protected int blockY; + protected int lastBlockX; + protected int lastBlockY; protected int targetX; protected int targetY; protected FacingDirection direction = FacingDirection.WEST; @@ -40,7 +49,16 @@ public Entity(Zone zone) { } public void tick(float deltaTime) { - // Override + // Update block position + lastBlockX = blockX; + lastBlockY = blockY; + blockX = (int)x; + blockY = (int)y; + + // Check if block position has changed + if(lastBlockX != blockX || lastBlockY != blockY) { + blockPositionChanged(); + } } public void die(Player killer) { @@ -67,6 +85,21 @@ public void damage(float amount, Player attacker) { lastDamagedAt = System.currentTimeMillis(); } + public void blockPositionChanged() { + // Check for touchplates + if(zone != null && zone.isChunkLoaded(blockX, blockY)) { + MetaBlock metaBlock = zone.getMetaBlock(blockX, blockY); + Block block = zone.getBlock(blockX, blockY); + Item item = block.getFrontItem(); + int mod = block.getFrontMod(); + + // Trigger a switch interaction if the entity stepped on a touchplate + if(item.hasUse(ItemUseType.TRIGGER)) { + ItemUseType.SWITCH.getInteraction().interact(zone, this, blockX, blockY, Layer.FRONT, item, mod, metaBlock, null, null); + } + } + } + public boolean canSee(Entity other) { return canSee((int)other.getX(), (int)other.getY()); } @@ -80,7 +113,7 @@ public boolean inRange(Entity other, float range) { return inRange(other.getX(), other.getY(), range); } - public boolean inRange(float x, float y, float range) { + public boolean inRange(float x, float y, double range) { return MathUtils.inRange(this.x, this.y, x, y, range); } @@ -204,6 +237,14 @@ public int getTargetY() { return targetY; } + public int getBlockX() { + return blockX; + } + + public int getBlockY() { + return blockY; + } + public void setDirection(FacingDirection direction) { this.direction = direction; } @@ -228,6 +269,10 @@ public Zone getZone() { return zone; } + public final boolean isPlayer() { + return this instanceof Player; // Not very OOP + } + /** * @return A {@link Map} containing all the data necessary for use in {@link EntityStatusMessage}. */ diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java index 9953c74b..bc75fb3d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java @@ -52,6 +52,7 @@ public class Npc extends Entity { private Vector2i mountBlock; private Entity owner; private Entity target; + private boolean artificial; private long lastBehavedAt = System.currentTimeMillis(); private long lastTrackedAt = System.currentTimeMillis(); @@ -120,6 +121,7 @@ public Npc(Zone zone, EntityConfig config) { @Override public void tick(float deltaTime) { + super.tick(deltaTime); long now = System.currentTimeMillis(); // Clear expired recent attacks @@ -148,7 +150,7 @@ public void tick(float deltaTime) { @Override public void die(Player killer) { // Grant loot & track kill - if(killer != null) { + if(!artificial && killer != null) { if(!isPlayerPlaced()) { // Track assists for(Player attacker : recentAttacks.keySet()) { @@ -176,7 +178,11 @@ public void die(Player killer) { MetaBlock metaBlock = zone.getMetaBlock(guardBlock.getX(), guardBlock.getY()); if(metaBlock != null) { - MapHelper.getList(metaBlock.getMetadata(), "!", Collections.emptyList()).remove(typeName); + List guards = MapHelper.getList(metaBlock.getMetadata(), "!"); + + if(guards != null) { + guards.remove(typeName); + } } } @@ -372,6 +378,14 @@ public Entity getTarget() { return target; } + public void setArtificial(boolean artificial) { + this.artificial = artificial; + } + + public boolean isArtificial() { + return artificial; + } + public void setSpeed(float speed) { this.speed = speed; } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java index 0c2221a2..f3bab42a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java @@ -98,6 +98,7 @@ public class Player extends Entity implements CommandExecutor { private List nameChanges; private List mutes; private List bans; + private Set lootCodes; private Set achievements; private Map ignoredHints; private Map skills; @@ -112,11 +113,13 @@ public class Player extends Entity implements CommandExecutor { private String clientVersion; private Placement lastPlacement; private Item heldItem = Item.AIR; - private Vector2i spawnPoint = new Vector2i(0, 0); + private int spawnX; + private int spawnY; private int teleportX; private int teleportY; private boolean stealth; private boolean godMode; + private boolean customSpawn; private long lastHeartbeat; private long lastTrackedEntityUpdate; private Zone nextZone; @@ -139,6 +142,7 @@ protected Player(String documentId, PlayerConfigFile config) { this.nameChanges = config.getNameChanges(); this.mutes = config.getMutes(); this.bans = config.getBans(); + this.lootCodes = config.getLootCodes(); this.achievements = config.getAchievements(); this.ignoredHints = config.getIgnoredHints(); this.skills = config.getSkills(); @@ -160,6 +164,7 @@ public Player(String documentId, String name, Zone zone) { this.nameChanges = new ArrayList<>(); this.mutes = new ArrayList<>(); this.bans = new ArrayList<>(); + this.lootCodes = new HashSet<>(); this.achievements = new HashSet<>(); this.ignoredHints = new HashMap<>(); this.skills = new HashMap<>(); @@ -175,6 +180,7 @@ private static Player fromId(String id) { @Override public void tick(float deltaTime) { + super.tick(deltaTime); long now = System.currentTimeMillis(); statistics.trackPlayTime(deltaTime); @@ -258,17 +264,29 @@ public Map getStatusConfig() { * Called by {@link Zone#addEntity(Entity)} when the player is added to it. */ public void onZoneChanged() { - // TODO handle spawns better + // Set spawn location + if(customSpawn) { + x = spawnX; + y = spawnY; + } + MetaBlock spawn = zone.getRandomSpawnBlock(); if(spawn == null) { - x = zone.getWidth() / 2; - y = 2; + spawnX = zone.getWidth() / 2; + spawnY = 2; } else { - x = spawn.getX() + 1; - y = spawn.getY(); + spawnX = spawn.getX() + 1; + spawnY = spawn.getY(); } + if(!customSpawn) { + x = spawnX; + y = spawnY; + } + + customSpawn = false; + // Set skills for new players for(Skill skill : Skill.values()) { if(!skills.containsKey(skill)) { @@ -289,8 +307,6 @@ public void onZoneChanged() { inventory.moveItemToContainer(jetpack, ContainerType.ACCESSORIES, 0); } - spawnPoint.setX((int)x); - spawnPoint.setY((int)y); sendMessage(new ConfigurationMessage(id, getClientConfig(), GameConfiguration.getClientConfig(this), zone.getClientConfig(this))); sendMessage(new ZoneStatusMessage(zone.getStatusConfig())); sendMessage(new ZoneStatusMessage(zone.getStatusConfig())); @@ -416,7 +432,14 @@ public void sendMessageToPeers(Message message) { } public void changeZone(Zone zone) { + changeZone(zone, -1, -1); + } + + public void changeZone(Zone zone, int x, int y) { nextZone = zone; + spawnX = x; + spawnY = y; + customSpawn = x != -1 && y != -1; sendMessage(new EventMessage("playerWillChangeZone", null)); kick("Teleporting...", true); } @@ -506,11 +529,9 @@ public void respawn() { setHealth(getMaxHealth()); } - int x = spawnPoint.getX(); - int y = spawnPoint.getY(); - sendMessage(new PlayerPositionMessage(x, y)); + sendMessage(new PlayerPositionMessage(spawnX, spawnY)); sendMessageToPeers(new EntityStatusMessage(this, EntityStatus.REVIVED)); - zone.sendMessage(new EffectMessage(x, y, "spawn", 20)); + zone.sendMessage(new EffectMessage(spawnX, spawnY, "spawn", 20)); } /** @@ -605,6 +626,8 @@ public void trackPlacement(int x, int y, Item item) { if(lastPlacement != null) { if(item.hasUse(ItemUseType.SWITCHED) && !item.hasUse(ItemUseType.SWITCH)) { linked = tryLinkSwitchedItem(x, y, item); + } else if(item.hasUse(ItemUseType.TRANSMITTED)) { + linked = tryLinkTransmittedItem(x, y, item); } } @@ -619,7 +642,7 @@ private boolean tryLinkSwitchedItem(int x, int y, Item item) { Item pItem = lastPlacement.getItem(); boolean linked = false; - if(pItem.hasUse(ItemUseType.SWITCH)) { + if(pItem.hasUse(ItemUseType.SWITCH, ItemUseType.TRIGGER)) { MetaBlock metaBlock = zone.getMetaBlock(pX, pY); Map metadata = metaBlock == null ? null : metaBlock.getMetadata(); @@ -642,6 +665,39 @@ private boolean tryLinkSwitchedItem(int x, int y, Item item) { return linked; } + private boolean tryLinkTransmittedItem(int x, int y, Item item) { + int pX = lastPlacement.getX(); + int pY = lastPlacement.getY(); + Item pItem = lastPlacement.getItem(); + + // Do nothing if the last placed item is not a transmitter + if(!pItem.hasUse(ItemUseType.TRANSMIT)) { + return false; + } + + int maxTransmitDistance = getTotalSkillLevel(Skill.ENGINEERING) * 10; + + // Notify the player if the distance is beyond the maximum transmit distance + if(!isGodMode() && !MathUtils.inRange(x, y, pX, pY, maxTransmitDistance)) { + notify(String.format("You can only transmit %s blocks at your current engineering level.", maxTransmitDistance)); + return false; + } + + MetaBlock metaBlock = zone.getMetaBlock(pX, pY); + Map metadata = metaBlock == null ? null : metaBlock.getMetadata(); + + // Do nothing if metadata is null for whatever reason + if(metadata == null) { + return false; + } + + // Link transmitter to beacon + MapHelper.appendList(metadata, ">", Arrays.asList(x, y)); // Make it a list for compatibility reasons + zone.updateBlock(pX, pY, Layer.FRONT, pItem, 1, null, metadata); + lastPlacement = null; + return true; + } + public double getMiningRange() { return 5 + getTotalSkillLevel(Skill.MINING) / 3.0; } @@ -662,6 +718,13 @@ public double getMiningBonusChance(MiningBonus bonus) { return bonus.getChance() * (getTotalSkillLevel(bonus.getSkill()) / (double)MAX_SKILL_LEVEL) * heldItem.getToolBonus(); } + /** + * @return The hash to be stored in blocks placed by this player. + */ + public int getBlockHash() { + return 1 + ((documentId.hashCode() & 2047) % 2047); + } + public String getDocumentId() { return documentId; } @@ -700,6 +763,18 @@ protected List getAuthTokens() { return authTokens; } + public void addLootCode(String lootCode) { + lootCodes.add(lootCode); + } + + public boolean hasLootCode(String lootCode) { + return lootCodes.contains(lootCode); + } + + public Set getLootCodes() { + return Collections.unmodifiableSet(lootCodes); + } + public void trackNameChange(String newName) { nameChanges.add(new NameChange(newName, name)); } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerConfigFile.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerConfigFile.java index 76c64430..e786466f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerConfigFile.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerConfigFile.java @@ -34,6 +34,7 @@ public class PlayerConfigFile { private List nameChanges = new ArrayList<>(); private List mutes = new ArrayList<>(); private List bans = new ArrayList<>(); + private Set lootCodes = new HashSet<>(); private Set achievements = new HashSet<>(); private Map ignoredHints = new HashMap<>(); private Map skills = new HashMap<>(); @@ -57,6 +58,7 @@ public PlayerConfigFile(Player player) { this.nameChanges = player.getNameChanges(); this.mutes = player.getMutes(); this.bans = player.getBans(); + this.lootCodes = player.getLootCodes(); this.achievements = player.getAchievements(); this.ignoredHints = player.getIgnoredHints(); this.skills = player.getSkills(); @@ -135,6 +137,11 @@ public PlayerStatistics getStatistics() { return statistics; } + @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) + public Set getLootCodes() { + return lootCodes; + } + @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) public Set getAchievements() { return achievements; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/Item.java b/gameserver/src/main/java/brainwine/gameserver/item/Item.java index fb00377c..2014db19 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/Item.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/Item.java @@ -16,6 +16,7 @@ import brainwine.gameserver.entity.player.Skill; import brainwine.gameserver.util.Pair; import brainwine.gameserver.util.Vector2i; +import brainwine.gameserver.util.WeightedMap; // TODO I don't like some parts of this, maybe they can be reworked. @JsonIgnoreProperties(ignoreUnknown = true) @@ -77,6 +78,9 @@ public class Item { @JsonProperty("power") private float power; + @JsonProperty("toughness") + private float toughness; + @JsonProperty("earthy") private boolean earthy; @@ -95,6 +99,9 @@ public class Item { @JsonProperty("custom_place") private boolean customPlace; + @JsonProperty("field_place") + private boolean fieldPlace; + @JsonProperty("base") private boolean base; @@ -149,6 +156,15 @@ public class Item { @JsonProperty("damage") private Pair damageInfo; + @JsonProperty("timer") + private Pair timer; + + @JsonProperty("timer_delay") + private int timerDelay; + + @JsonProperty("timer_mine") + private boolean processTimerOnBreak; + @JsonProperty("ingredients") private List craftingIngredients = new ArrayList<>(); @@ -161,6 +177,9 @@ public class Item { @JsonProperty("convert") private Map conversions = new HashMap<>(); + @JsonProperty("spawn_entity") + private WeightedMap entitySpawns = new WeightedMap<>(); + @JsonCreator private Item(@JsonProperty(value = "id", required = true) String id, @JsonProperty(value = "code", required = true) int code) { @@ -315,6 +334,10 @@ public float getPower() { return power; } + public float getToughness() { + return toughness; + } + public boolean isEarthy() { return earthy; } @@ -343,6 +366,10 @@ public boolean hasCustomPlace() { return customPlace; } + public boolean canPlaceInField() { + return fieldPlace; + } + public boolean isWhole() { return whole; } @@ -439,6 +466,26 @@ public float getDamage() { return isWeapon() ? damageInfo.getLast() : 0; } + public boolean hasTimer() { + return timer != null; + } + + public String getTimerType() { + return hasTimer() ? timer.getFirst() : null; + } + + public int getTimerValue() { + return hasTimer() ? timer.getLast() : 0; + } + + public int getTimerDelay() { + return timerDelay; + } + + public boolean shouldProcessTimerOnBreak() { + return processTimerOnBreak; + } + public boolean isCraftable() { return !craftingIngredients.isEmpty(); } @@ -476,4 +523,12 @@ public Map getUses() { public Map getConversions() { return conversions.entrySet().stream().collect(Collectors.toMap(entry -> entry.getKey().get(), entry -> entry.getValue().get())); } + + public boolean hasEntitySpawns() { + return !entitySpawns.isEmpty(); + } + + public WeightedMap getEntitySpawns() { + return entitySpawns; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java index f2995fed..7ba29082 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java @@ -3,27 +3,61 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; +import brainwine.gameserver.item.interactions.BurstInteraction; +import brainwine.gameserver.item.interactions.ChangeInteraction; +import brainwine.gameserver.item.interactions.ContainerInteraction; +import brainwine.gameserver.item.interactions.DialogInteraction; +import brainwine.gameserver.item.interactions.ItemInteraction; +import brainwine.gameserver.item.interactions.SpawnInteraction; +import brainwine.gameserver.item.interactions.SpawnTeleportInteraction; +import brainwine.gameserver.item.interactions.SwitchInteraction; +import brainwine.gameserver.item.interactions.TargetTeleportInteraction; +import brainwine.gameserver.item.interactions.TeleportInteraction; +import brainwine.gameserver.item.interactions.TransmitInteraction; + +/** + * Much like with {@link Action}, block interactions depend on their use type. + */ public enum ItemUseType { AFTERBURNER, - CONTAINER, - CREATE_DIALOG, - DIALOG, + BURST(new BurstInteraction()), + CONTAINER(new ContainerInteraction()), + CREATE_DIALOG(new DialogInteraction(true)), + DESTROY, + DIALOG(new DialogInteraction(false)), GUARD, - CHANGE, + CHANGE(new ChangeInteraction()), FIELDABLE, FLY, MULTI, + PLENTY, PROTECTED, PUBLIC, - SWITCH, + SPAWN(new SpawnInteraction()), + SPAWN_TELEPORT(new SpawnTeleportInteraction()), + SWITCH(new SwitchInteraction()), SWITCHED, - TELEPORT, + TARGET_TELEPORT(new TargetTeleportInteraction()), + TELEPORT(new TeleportInteraction()), + TRIGGER, + TRANSMIT(new TransmitInteraction()), + TRANSMITTED, ZONE_TELEPORT, @JsonEnumDefaultValue UNKNOWN; - + + private final ItemInteraction interaction; + + private ItemUseType(ItemInteraction interaction) { + this.interaction = interaction; + } + + private ItemUseType() { + this(null); + } + @JsonCreator public static ItemUseType fromId(String id) { String formatted = id.toUpperCase().replace(" ", "_"); @@ -36,4 +70,8 @@ public static ItemUseType fromId(String id) { return UNKNOWN; } + + public ItemInteraction getInteraction() { + return interaction; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/BurstInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/BurstInteraction.java new file mode 100644 index 00000000..ecd1b71a --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/BurstInteraction.java @@ -0,0 +1,63 @@ +package brainwine.gameserver.item.interactions; + +import java.util.Map; + +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.entity.player.Skill; +import brainwine.gameserver.item.DamageType; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.Layer; +import brainwine.gameserver.util.MapHelper; +import brainwine.gameserver.zone.Block; +import brainwine.gameserver.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; + +/** + * Interaction handler for items that explode if you get too close + */ +@SuppressWarnings("unchecked") +public class BurstInteraction implements ItemInteraction { + + @Override + public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, + Object config, Object[] data) { + // Do nothing if entity is not a player + if(!entity.isPlayer()) { + return; + } + + // Do nothing if data is invalid + if(!(config instanceof Map)) { + return; + } + + Player player = (Player)entity; + Map configMap = (Map)config; + boolean dodge = MapHelper.getBoolean(configMap, "dodge"); + + // Do nothing if the player is lucky enough :) + if(dodge && Math.random() * Player.MAX_SKILL_LEVEL <= player.getTotalSkillLevel(Skill.AGILITY) / 2.0F) { + return; + } + + boolean natural = MapHelper.getBoolean(configMap, "natural"); + boolean enemy = MapHelper.getBoolean(configMap, "enemy"); + Block block = zone.getBlock(x, y); + + // Check if the block has to be be natural or triggered by an enemy + if((natural && !block.isNatural()) || (enemy && (player.isStealthy() || block.getOwnerHash() == player.getBlockHash()))) { + return; + } + + DamageType damageType = DamageType.fromName(MapHelper.getString(configMap, "damage_type")); + String effect = MapHelper.getString(configMap, "effect", "bomb"); + float range = MapHelper.getFloat(configMap, "range"); + float damage = MapHelper.getFloat(configMap, "damage"); + boolean destructive = MapHelper.getBoolean(configMap, "destructive"); + + // Create explosion and destroy block + zone.explode(x, y, range, player, destructive, damage, damageType, effect); + zone.updateBlock(x, y, layer, 0); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ChangeInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ChangeInteraction.java new file mode 100644 index 00000000..5b07ae8e --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ChangeInteraction.java @@ -0,0 +1,26 @@ +package brainwine.gameserver.item.interactions; + +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.Layer; +import brainwine.gameserver.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; + +/** + * Interaction handler for blocks that can change between two states + */ +public class ChangeInteraction implements ItemInteraction { + + @Override + public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, + Object config, Object[] data) { + // Do nothing if entity is not a player + if(!entity.isPlayer()) { + return; + } + + Player player = (Player)entity; + zone.updateBlock(x, y, layer, item, mod == 0 ? 1 : 0, player); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java new file mode 100644 index 00000000..192db113 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java @@ -0,0 +1,88 @@ +package brainwine.gameserver.item.interactions; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.entity.Entity; +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.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; + +/** + * Interaction handler for lootable containers + */ +public class ContainerInteraction implements ItemInteraction { + + @Override + public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, + Object config, Object[] data) { + // Do nothing if entity is not a player + if(!entity.isPlayer()) { + return; + } + + // Check if the right data is present + if(metaBlock == null || data != null) { + return; + } + + Player player = (Player)entity; + String dungeonId = metaBlock.getStringProperty("@"); + + // Check if container is protected by a dungeon + if(item.hasUse(ItemUseType.FIELDABLE) && dungeonId != null && zone.isDungeonIntact(dungeonId)) { + player.notify("This container is secured by protectors in the area."); + return; + } + + boolean plenty = item.hasUse(ItemUseType.PLENTY); + String lootCode = metaBlock.getStringProperty("y"); + + // Check loot code + if(plenty) { + if(lootCode == null) { + player.notify("This chest cannot be plundered."); + return; + } + + if(player.hasLootCode(lootCode)) { + player.notify("You've already plundered this chest."); + return; + } + } + + String specialItem = metaBlock.getStringProperty("$"); + + // Award loot + if(specialItem != null) { + if(specialItem.equals("?")) { + Loot loot = metaBlock.hasProperty("l") ? new Loot(Item.get(metaBlock.getStringProperty("l")), metaBlock.getIntProperty("q")) + : GameServer.getInstance().getLootManager().getRandomLoot(player, item.getLootCategories()); + int experience = metaBlock.getIntProperty("xp"); + + if(loot != null) { + if(plenty) { + player.addLootCode(lootCode); + } else { + metaBlock.removeProperty("$"); + metaBlock.removeProperty("xp"); + } + + player.awardLoot(loot, item.getLootGraphic()); + player.addExperience(experience); + player.getStatistics().trackContainerLooted(item); + } else { + player.notify("No eligible loot could be found for this container."); + } + } + } + + // Update container mod + if(!plenty && !metaBlock.hasProperty("$")) { + Player owner = GameServer.getInstance().getPlayerManager().getPlayerById(metaBlock.getOwner()); + zone.updateBlock(x, y, Layer.FRONT, item, 0, owner, metaBlock.getMetadata()); + } + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java new file mode 100644 index 00000000..cd999e33 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java @@ -0,0 +1,98 @@ +package brainwine.gameserver.item.interactions; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.Layer; +import brainwine.gameserver.util.MapHelper; +import brainwine.gameserver.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; + +/** + * Interaction handler for blocks that may be configured through a dialog + */ +@SuppressWarnings("unchecked") +public class DialogInteraction implements ItemInteraction { + + private boolean creationOnly; + + public DialogInteraction(boolean creationOnly) { + this.creationOnly = creationOnly; + } + + @Override + public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, + Object config, Object[] data) { + // Do nothing if entity is not a player + if(!entity.isPlayer()) { + return; + } + + // Do nothing if the required data isn't present + if(data == null || !(config instanceof Map)) { + return; + } + + // Do nothing if this block has already been configured and cannot be re-configured with this interaction + if(creationOnly && metaBlock != null && metaBlock.getBooleanProperty("cd")) { + return; + } + + Player player = (Player)entity; + Map configMap = (Map)config; + String target = MapHelper.getString(configMap, "target", "none"); + + // Do nothing for now if the target isn't the block's metadata + if(!target.equals("meta")) { + player.notify("Sorry, this action isn't implemented yet."); + return; + } + + // Update block metadata + Map metadata = new HashMap<>(); + List> sections = MapHelper.getList(configMap, "sections"); + + if(metaBlock != null) { + metadata.putAll(metaBlock.getMetadata()); + } + + if(sections != null && data.length == sections.size()) { + for(int i = 0; i < sections.size(); i++) { + Map section = sections.get(i); + String key = MapHelper.getString(section, "input.key"); + + if(key != null) { + String text = String.valueOf(data[i]); + + // Get rid of text if player is currently muted + if(player.isMuted() && MapHelper.getBoolean(section, "input.sanitize")) { + text = text.replaceAll(".", "*"); + } + + metadata.put(key, text); + } else if(MapHelper.getBoolean(section, "input.mod")) { + List 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); + } + } + } + } + + // Set configured flag + if(creationOnly) { + metadata.put("cd", true); + } + + // Update meta block + zone.setMetaBlock(x, y, item, player, metadata); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ItemInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ItemInteraction.java new file mode 100644 index 00000000..ea95a942 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ItemInteraction.java @@ -0,0 +1,13 @@ +package brainwine.gameserver.item.interactions; + +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.Layer; +import brainwine.gameserver.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; + +public interface ItemInteraction { + + public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, + Object config, Object[] data); +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnInteraction.java new file mode 100644 index 00000000..e7633c7f --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnInteraction.java @@ -0,0 +1,39 @@ +package brainwine.gameserver.item.interactions; + +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.EntityConfig; +import brainwine.gameserver.entity.EntityRegistry; +import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.Layer; +import brainwine.gameserver.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; + +/** + * Interaction handler for items that can spawn entities + */ +public class SpawnInteraction implements ItemInteraction { + + @Override + public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, + Object config, Object[] data) { + // Do nothing if item can't spawn entities + if(!item.hasEntitySpawns() || mod != 0) { + return; + } + + EntityConfig entityConfig = EntityRegistry.getEntityConfig(item.getEntitySpawns().next()); + + // Do nothing if type is invalid + if(entityConfig == null) { + return; + } + + // Spawn the entity + Npc npc = new Npc(zone, entityConfig); + zone.spawnEntity(npc, x, y); + + // Update block mod + zone.updateBlock(x, y, layer, item, 1); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnTeleportInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnTeleportInteraction.java new file mode 100644 index 00000000..f8f3c63f --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnTeleportInteraction.java @@ -0,0 +1,39 @@ +package brainwine.gameserver.item.interactions; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.Layer; +import brainwine.gameserver.zone.Biome; +import brainwine.gameserver.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; + +/** + * Interaction handler for that one white teleporter in the tutorial world + */ +public class SpawnTeleportInteraction implements ItemInteraction { + + @Override + public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, + Object config, Object[] data) { + // Do nothing if entity is not a player + if(!entity.isPlayer()) { + return; + } + + Player player = (Player)entity; + + // Find a random suitable zone + Zone targetZone = GameServer.getInstance().getZoneManager().getRandomZone(z -> z.getBiome() == Biome.PLAIN); + + // Notify the player if no zone could be found + if(targetZone == null) { + player.notify("Couldn't find a suitable zone to teleport to. Try again later."); + return; + } + + // Teleport the player to the target zone + player.changeZone(targetZone); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java new file mode 100644 index 00000000..3fb9ff53 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java @@ -0,0 +1,215 @@ +package brainwine.gameserver.item.interactions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.commons.text.WordUtils; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.EntityConfig; +import brainwine.gameserver.entity.EntityRegistry; +import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.DamageType; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.ItemUseType; +import brainwine.gameserver.item.Layer; +import brainwine.gameserver.server.messages.BlockMetaMessage; +import brainwine.gameserver.server.messages.EffectMessage; +import brainwine.gameserver.util.MapHelper; +import brainwine.gameserver.util.Vector2i; +import brainwine.gameserver.zone.Chunk; +import brainwine.gameserver.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; + +/** + * Interaction handler for switches + */ +public class SwitchInteraction implements ItemInteraction { + + @Override + public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, + Object config, Object[] data) { + // Do nothing if the required data isn't present + if(data != null || metaBlock == null) { + return; + } + + int timer = metaBlock.getIntProperty("t"); + + // Do nothing if this switch has a timer and is already flipped + if(timer > 0 && mod % 2 == 1) { + return; + } + + // Show configured message to nearby players + String message = metaBlock.getStringProperty("m"); + + if(message != null && !message.isEmpty()) { + float effectX = x + item.getBlockWidth() / 2.0F; + float effectY = y - item.getBlockHeight() + 1; + zone.sendMessageToChunk(new EffectMessage(effectX, effectY, "emote", message), zone.getChunk(x, y)); + } + + // Prepare list of targets + List> positions = MapHelper.getList(metaBlock.getMetadata(), ">", Collections.emptyList()); + List targets = new ArrayList<>(); + targets.add(new Vector2i(x, y)); + positions.stream().map(position -> new Vector2i(position.get(0), position.get(1))).forEach(targets::add); + int switchedMod = mod % 2 == 0 ? mod + 1 : mod - 1; + + // Switch all target blocks + for(Vector2i target : targets) { + switchBlock(zone, entity, target.getX(), target.getY(), switchedMod, metaBlock); + } + + // Create block timer if this is a timed switch + if(timer > 0) { + int unswitchedMod = switchedMod % 2 == 0 ? switchedMod + 1 : switchedMod - 1; + + zone.addBlockTimer(x, y, timer * 1000, () -> { + for(Vector2i target : targets) { + switchBlock(zone, entity, target.getX(), target.getY(), unswitchedMod, metaBlock); + } + }); + } + } + + private void switchBlock(Zone zone, Entity entity, int x, int y, int mod, MetaBlock switchMeta) { + // Do nothing if the target chunk isn't loaded + if(!zone.isChunkLoaded(x, y)) { + return; + } + + MetaBlock metaBlock = zone.getMetaBlock(x, y); + + // Do nothing if there is no metadata + if(metaBlock == null) { + return; + } + + Player owner = metaBlock == null ? null : GameServer.getInstance().getPlayerManager().getPlayerById(metaBlock.getOwner()); + Map metadata = metaBlock == null ? null : metaBlock.getMetadata(); + Item item = metaBlock.getItem(); + Object config = item.getUse(ItemUseType.SWITCHED); + + if(config instanceof String) { + String type = (String)config; + + // Not the prettiest way to do this but it will have to do. + switch(type.toLowerCase()) { + case "spawner": switchSpawner(zone, metaBlock); break; + case "exploder": switchExploder(zone, entity, metaBlock); break; + case "messagesign": switchSign(zone, entity, metaBlock, switchMeta); break; + default: break; + } + } else if(item.hasUse(ItemUseType.SWITCH, ItemUseType.SWITCHED, ItemUseType.TRIGGER)) { + zone.updateBlock(x, y, Layer.FRONT, item, mod, owner, metadata); + } + } + + private void switchSpawner(Zone zone, MetaBlock metaBlock) { + // Kill existing entity + if(metaBlock.hasProperty("eid")) { + Entity entity = zone.getEntity(metaBlock.getIntProperty("eid")); + + if(entity != null && !entity.isDead()) { + entity.setHealth(0); + zone.sendMessageToChunk(new EffectMessage(entity.getX(), entity.getY(), "bomb-teleport", 4), zone.getChunk(metaBlock.getX(), metaBlock.getY())); + } + } + + Object config = metaBlock.getItem().getUse(ItemUseType.SPAWN); + + // Do nothing if use config data is invalid + if(!(config instanceof Map)) { + return; + } + + // Determine entity type + String entityType = MapHelper.getString((Map)config, metaBlock.getStringProperty("e")); + EntityConfig entityConfig = EntityRegistry.getEntityConfig(entityType); + + // Do nothing if entity config doesn't exist + if(entityConfig == null) { + return; + } + + // Create & spawn the entity + Npc npc = new Npc(zone, entityConfig); + npc.setArtificial(true); + zone.spawnEntity(npc, metaBlock.getX(), metaBlock.getY(), true); + + // Track entity id in spawner metadata + metaBlock.setProperty("eid", npc.getId()); + } + + // TODO exploders were used to create lag machines back in the day, so maybe we should put a cooldown on this + private void switchExploder(Zone zone, Entity entity, MetaBlock metaBlock) { + String type = metaBlock.getStringProperty("e"); + + // Do nothing if explosion type doesn't exist in block metadata + if(type == null) { + return; + } + + // Create explosion + DamageType damageType = DamageType.fromName(type); + String effect = String.format("bomb-%s", type.toLowerCase()); + zone.explode(metaBlock.getX(), metaBlock.getY(), 6, entity, false, 6, damageType, effect); + } + + private void switchSign(Zone zone, Entity entity, MetaBlock metaBlock, MetaBlock switchMeta) { + String message = switchMeta.hasProperty("m") ? switchMeta.getStringProperty("m").trim() : ""; + boolean lock = metaBlock.getStringProperty("lock").equalsIgnoreCase("yes"); + Item item = metaBlock.getItem(); + + // Check and update lock status + if(lock) { + boolean locked = metaBlock.getBooleanProperty("locked"); + + if(!message.isEmpty()) { + if(locked) { + return; + } + + metaBlock.setProperty("locked", true); + } else if(locked) { + metaBlock.removeProperty("locked"); + } + } + + // Update sign text + String name = entity.getName(); + + if(name != null) { + message = message.replaceAll("%t%", name); + } + + String separator = "\n"; + String[] keys = {"t1", "t2", "t3", "t4"}; + String[] segments = WordUtils.wrap(message, 20, separator, true).split(separator, 4); + + for(int i = 0; i < keys.length; i++) { + String key = keys[i]; + String text = i < segments.length ? segments[i] : ""; + int separatorIndex = text.lastIndexOf(separator); + + if(separatorIndex != -1) { + text = text.substring(0, separatorIndex); + } + + metaBlock.setProperty(key, text); + } + + // Send data to players + float effectX = metaBlock.getX() + (float)item.getBlockWidth() / 2; + float effectY = metaBlock.getY() - (float)item.getBlockHeight() / 2 + 1; + Chunk chunk = zone.getChunk(metaBlock.getX(), metaBlock.getY()); + zone.sendMessageToChunk(new EffectMessage(effectX, effectY, "area steam", 10), chunk); + zone.sendMessageToChunk(new BlockMetaMessage(metaBlock), chunk); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/TargetTeleportInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/TargetTeleportInteraction.java new file mode 100644 index 00000000..01f8033d --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/TargetTeleportInteraction.java @@ -0,0 +1,102 @@ +package brainwine.gameserver.item.interactions; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.dialog.Dialog; +import brainwine.gameserver.dialog.DialogSection; +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.Layer; +import brainwine.gameserver.zone.Biome; +import brainwine.gameserver.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; + +public class TargetTeleportInteraction implements ItemInteraction { + + @Override + public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, + Object config, Object[] data) { + // Do nothing if entity is not a player + if(!entity.isPlayer()) { + return; + } + + // Do nothing if data is invalid + if(data != null) { + return; + } + + Player player = (Player)entity; + Zone targetZone = zone; + String zoneName = metaBlock.getStringProperty("pz"); + + // Validate target zone + if(zoneName != null) { + targetZone = GameServer.getInstance().getZoneManager().getZoneByName(zoneName); + + if(targetZone == null) { + player.notify(String.format("Cannot locate world '%s', please recalibrate.", zoneName)); + return; + } + } + + // Parse target position + int targetX = -1; + int targetY = metaBlock.getIntProperty("py") + (targetZone.getBiome() == Biome.DEEP ? -1000 : 200); + int centerX = zone.getWidth() / 2; + + try { + String strX = metaBlock.getStringProperty("px"); + + if(strX != null) { + if(strX.endsWith("w")) { + targetX = centerX - Integer.parseInt(strX.replace("w", "")); + } else { + targetX = centerX + Integer.parseInt(strX.replace("e", "")); + } + } + } catch(NumberFormatException e) { + // Discard silently + } + + // Do nothing if target is out of bounds + if(!targetZone.areCoordinatesInBounds(targetX, targetY)) { + player.notify("Cannot locate destination, please recalibrate."); + return; + } + + // Do nothing if target location is unexplored + if(!player.isGodMode() && !targetZone.isAreaExplored(targetX, targetY)) { + player.notify("That area hasn't been explored yet."); + return; + } + + // Do nothing if target location is protected + if(!player.isGodMode() && targetZone.isBlockProtected(targetX, targetY, player)) { + player.notify("That area is protected."); + return; + } + + // Teleport the player to the target location + if(targetZone == zone) { + player.teleport(targetX, targetY); + } else { + // Create confirmation dialog + Dialog dialog = new Dialog() + .setActions("yesno") + .addSection(new DialogSection() + .setTitle("Attention") + .setText(String.format("Teleport to world '%s'?", targetZone.getName()))); + + // Show confirmation dialog for zone change + Zone _targetZone = targetZone; + int _targetX = targetX; + int _targetY = targetY; + player.showDialog(dialog, input -> { + if(input.length == 1 && input[0].equals("Yes")) { + player.changeZone(_targetZone, _targetX, _targetY); + } + }); + } + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/TeleportInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/TeleportInteraction.java new file mode 100644 index 00000000..70989e27 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/TeleportInteraction.java @@ -0,0 +1,56 @@ +package brainwine.gameserver.item.interactions; + +import brainwine.gameserver.entity.Entity; +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.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; + +/** + * Interaction handler for teleporters + */ +public class TeleportInteraction implements ItemInteraction { + + @Override + public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, + Object config, Object[] data) { + // Do nothing if entity is not a player + if(!entity.isPlayer()) { + return; + } + + Player player = (Player)entity; + + // Try to repair teleporter + if(mod == 0) { + zone.updateBlock(x, y, layer, item, 1); + player.getStatistics().trackDiscovery(item); + player.notify("You repaired a teleporter!", NotificationType.ACCOMPLISHMENT); + player.notifyPeers(String.format("%s repaired a teleporter.", player.getName()), NotificationType.SYSTEM); + return; + } + + // Verify data + if(data == null || data.length != 2 || mod != 1) { + return; + } + + int targetX = data[0] instanceof Integer ? (int)data[0] : -1; + int targetY = data[1] instanceof Integer ? (int)data[1] : -1; + MetaBlock targetMeta = zone.getMetaBlock(targetX, targetY); + + // Do nothing if target has no metadata + if(targetMeta == null) { + return; + } + + // Teleport the player if the target location is valid + if((targetMeta.getItem().hasUse(ItemUseType.TELEPORT) && zone.getBlock(targetX, targetY).getFrontMod() == 1) + || targetMeta.getItem().hasUse(ItemUseType.ZONE_TELEPORT)) { + player.teleport(targetX + 1, targetY); + } + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/TransmitInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/TransmitInteraction.java new file mode 100644 index 00000000..20bbc943 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/TransmitInteraction.java @@ -0,0 +1,58 @@ +package brainwine.gameserver.item.interactions; + +import java.util.Collections; +import java.util.List; + +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.ItemUseType; +import brainwine.gameserver.item.Layer; +import brainwine.gameserver.util.MapHelper; +import brainwine.gameserver.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; + +/** + * Interaction handler for target teleporters + */ +public class TransmitInteraction implements ItemInteraction { + + @Override + public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, + Object config, Object[] data) { + // Do nothing if entity is not a player + if(!entity.isPlayer()) { + return; + } + + // Do nothing if the required data isn't present + if(data != null || metaBlock == null) { + return; + } + + Player player = (Player)entity; + List> positions = MapHelper.getList(metaBlock.getMetadata(), ">", Collections.emptyList()); + + // Do nothing if there is no linked position + if(positions.isEmpty()) { + return; + } + + List position = positions.get(0); + int targetX = position.get(0); + int targetY = position.get(1); + + // Make sure that the target location is in bounds + if(!zone.areCoordinatesInBounds(targetX, targetY)) { + return; + } + + // Notify the player if the target beacon is missing + if(!zone.getBlock(targetX, targetY).getFrontItem().hasUse(ItemUseType.TRANSMITTED)) { + player.notify("There is no beacon at the target location."); + return; + } + + player.teleport(targetX, targetY); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/loot/Loot.java b/gameserver/src/main/java/brainwine/gameserver/loot/Loot.java index f1a39004..90f7bbf5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/loot/Loot.java +++ b/gameserver/src/main/java/brainwine/gameserver/loot/Loot.java @@ -28,6 +28,13 @@ public class Loot { @JsonCreator private Loot() {} + /** + * Arbitrary constructor for chests o' plenty + */ + public Loot(Item item, int quantity) { + this.items.put(item, quantity); + } + public Map getItems() { return items; } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/EffectMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/EffectMessage.java index ec3fc2c4..adae9165 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/EffectMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/EffectMessage.java @@ -10,12 +10,12 @@ public class EffectMessage extends Message { public int x; public int y; public String name; - public int count; + public Object data; - public EffectMessage(float x, float y, String name, int count) { + public EffectMessage(float x, float y, String name, Object data) { this.x = (int)(x * Entity.POSITION_MODIFIER); this.y = (int)(y * Entity.POSITION_MODIFIER); this.name = name; - this.count = count; + this.data = data; } } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index 777f5d6c..3c80eecc 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -5,6 +5,10 @@ import java.util.Map; import brainwine.gameserver.annotations.RequestInfo; +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.EntityConfig; +import brainwine.gameserver.entity.EntityRegistry; +import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.entity.player.NotificationType; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.entity.player.Skill; @@ -17,6 +21,7 @@ import brainwine.gameserver.item.ModType; import brainwine.gameserver.server.PlayerRequest; import brainwine.gameserver.server.messages.BlockChangeMessage; +import brainwine.gameserver.server.messages.EffectMessage; import brainwine.gameserver.server.messages.InventoryMessage; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.MathUtils; @@ -125,6 +130,34 @@ public void process(Player player) { } } + if(item.shouldProcessTimerOnBreak()) { + zone.processBlockTimer(x, y); + } + + // Pretty much only used for spawners + if(item.hasUse(ItemUseType.DESTROY)) { + Object config = item.getUse(ItemUseType.DESTROY); + + if(config instanceof String) { + String type = (String)config; + + switch(type.toLowerCase()) { + case "spawner": destroySpawner(zone, metaBlock); break; + default: break; + } + } + } + + // Check for entity spawns + if(item.hasEntitySpawns() && block.getMod(layer) == 0 && !item.hasTimer() && !item.hasUse(ItemUseType.SPAWN)) { + EntityConfig type = EntityRegistry.getEntityConfig(item.getEntitySpawns().next()); + + if(type != null) { + Npc npc = new Npc(zone, type); + zone.spawnEntity(npc, x, y); + } + } + zone.updateBlock(x, y, layer, 0, 0, player); player.getStatistics().trackItemMined(item); Item inventoryItem = item.getMod() == ModType.DECAY && block.getMod(layer) > 0 ? item.getDecayInventoryItem() : item.getInventoryItem(); @@ -151,6 +184,21 @@ public void process(Player player) { } } + private void destroySpawner(Zone zone, MetaBlock metaBlock) { + // Do nothing if spawner doesn't have an entity + if(!metaBlock.hasProperty("eid")) { + return; + } + + Entity entity = zone.getEntity(metaBlock.getIntProperty("eid")); + + // Kill entity if it exists + if(entity != null && !entity.isDead()) { + entity.setHealth(0); + zone.sendMessageToChunk(new EffectMessage(entity.getX(), entity.getY(), "bomb-teleport", 4), zone.getChunk(metaBlock.getX(), metaBlock.getY())); + } + } + private void fail(Player player, String reason) { player.notify(reason); Block block = player.getZone().getBlock(x, y); diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java index d74fbca4..c6cd0155 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java @@ -1,8 +1,14 @@ package brainwine.gameserver.server.requests; +import java.util.UUID; + import brainwine.gameserver.annotations.RequestInfo; +import brainwine.gameserver.entity.EntityConfig; +import brainwine.gameserver.entity.EntityRegistry; +import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.entity.player.Skill; +import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; import brainwine.gameserver.item.ModType; @@ -12,6 +18,7 @@ import brainwine.gameserver.util.MathUtils; import brainwine.gameserver.util.Pair; import brainwine.gameserver.zone.Block; +import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; @RequestInfo(id = 12) @@ -56,7 +63,7 @@ public void process(Player player) { return; } - if(!player.isGodMode() && zone.isBlockProtected(x, y, player)) { + if(!player.isGodMode() && !item.canPlaceInField() && zone.isBlockProtected(x, y, player)) { fail(player, "This block is protected."); return; } @@ -100,18 +107,108 @@ public void process(Player player) { player.getStatistics().trackItemPlaced(); player.trackPlacement(x, y, item); + // Create block timer if applicable + if(item.hasTimer()) { + createBlockTimer(zone, player); + } + // Process custom place if applicable if(item.hasCustomPlace()) { - processCustomPlace(player); + processCustomPlace(zone, player); } } - private void processCustomPlace(Player player) { - Zone zone = player.getZone(); + private void createBlockTimer(Zone zone, Player player) { + String type = item.getTimerType(); + int value = item.getTimerValue(); + Runnable task = null; + + switch(type) { + case "front mod": + task = () -> zone.updateBlock(x, y, layer, item, value); + break; + case "bomb": + task = () -> zone.explode(x, y, value, player, true, value, DamageType.FIRE, value >= 6 ? "bomb-large" : "bomb"); + break; + case "bomb-fire": + task = () -> zone.explode(x, y, value, player, false, value, DamageType.FIRE, "bomb-fire"); + break; + case "bomb-electric": + task = () -> zone.explode(x, y, value, player, false, value, DamageType.ENERGY, "bomb-electric"); + break; + case "bomb-frost": + task = () -> zone.explode(x, y, value, player, false, value, DamageType.COLD, "bomb-frost"); + break; + case "bomb-dig": + task = () -> { + zone.explode(x, y, value, player, "bomb-fire"); + int distance = value * 10; + + // Dig until we reach the maximum distance or hit a solid block + for(int i = 1; i <= distance; i++) { + if(!zone.digBlock(x, y + i)) { + break; + } + } + }; + break; + case "bomb-spawner": + task = () -> { + zone.explode(x, y, value, player, false, value, DamageType.FIRE, "bomb-fire"); + + // Spawn a bunch of entities + for(int i = 0; i < value; i++) { + EntityConfig entityType = EntityRegistry.getEntityConfig(item.getEntitySpawns().next()); + + if(entityType != null) { + Npc npc = new Npc(zone, entityType); + zone.spawnEntity(npc, x, y); + } + } + }; + break; + case "bomb-water": + case "bomb-acid": + case "bomb-lava": + task = () -> { + zone.explode(x, y, value, player, false, value, DamageType.FIRE, "bomb-large"); + Item liquid = Item.get(String.format("liquid/%s", type.replace("bomb-", ""))); + int range = 4; + + // Do nothing if there is no liquid to place + if(liquid.isAir()) { + return; + } + + // Place liquid blocks around the explosion + for(int i = x - range; i <= x + range; i++) { + for(int j = y - range; j <= y + range; j++) { + // Skip if not in range + if(!MathUtils.inRange(x, y, i, j, range)) { + continue; + } + + // Place liquid if target block isn't solid + if(!zone.isBlockSolid(i, j, true)) { + zone.updateBlock(i, j, Layer.LIQUID, liquid, 5); + } + } + } + }; + break; + default: + break; + } + if(task != null) { + zone.addBlockTimer(x, y, item.getTimerDelay() * 1000, task); + } + } + + private void processCustomPlace(Zone zone, Player player) { switch(item.getId()) { - // See if we can plug a maw or pipe case "building/plug": + // See if we can plug a maw or pipe Item baseItem = zone.getBlock(x, y).getBaseItem(); String plugged = baseItem.hasId("base/maw") ? "base/maw-plugged" : baseItem.hasId("base/pipe") ? "base/pipe-plugged" : null; @@ -122,6 +219,16 @@ private void processCustomPlace(Player player) { player.getStatistics().trackMawPlugged(); } + break; + case "containers/chest-plenty": + case "containers/sack-plenty": + // Create additional metadata for chests o' plenty + MetaBlock metaBlock = zone.getMetaBlock(x, y); + + if(metaBlock != null) { + metaBlock.setProperty("y", UUID.randomUUID().toString()); // Generate random loot code + metaBlock.setProperty("$", "?"); + } break; // No valid item; do nothing default: break; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockUseRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockUseRequest.java index 6f4a00cc..dd669bbb 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockUseRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockUseRequest.java @@ -1,27 +1,20 @@ 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.annotations.OptionalField; import brainwine.gameserver.annotations.RequestInfo; -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.item.interactions.ItemInteraction; 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") @RequestInfo(id = 21) public class BlockUseRequest extends PlayerRequest { @@ -36,10 +29,17 @@ public class BlockUseRequest extends PlayerRequest { public void process(Player player) { Zone zone = player.getZone(); + // Do nothing if player is dead or if the target chunk is not active if(player.isDead() || !player.isChunkActive(x, y)) { return; } + // Do nothing if player is too far away + if(!player.isGodMode() && !player.inRange(x, y, player.getMiningRange())) { + return; + } + + // Transform usage data if necessary if(data != null && data.length == 1 && data[0] instanceof Map) { data = ((Map)data[0]).values().toArray(); } @@ -49,6 +49,7 @@ public void process(Player player) { Item item = block.getItem(layer); int mod = block.getMod(layer); + // Check if block is owned by another player if(metaBlock != null && item.hasUse(ItemUseType.PROTECTED)) { Player owner = GameServer.getInstance().getPlayerManager().getPlayerById(metaBlock.getOwner()); @@ -56,154 +57,28 @@ public void process(Player player) { if(item.hasUse(ItemUseType.PUBLIC)) { String publicUse = item.getUse(ItemUseType.PUBLIC).toString(); + // TODO implement other cases switch(publicUse) { case "owner": player.notify(String.format("This %s is owned by %s.", - item.getTitle().toLowerCase(), owner == null ? "nobody.." : owner.getName())); + item.getTitle().toLowerCase(), owner == null ? "somebody else" : owner.getName())); break; } } else { player.notify("Sorry, that belongs to somebody else."); - return; } + + return; } } - for(Entry entry : item.getUses().entrySet()) { - ItemUseType use = entry.getKey(); - Object value = entry.getValue(); + // Try to interact with the block + item.getUses().forEach((use, config) -> { + ItemInteraction interaction = use.getInteraction(); - switch(use) { - case DIALOG: - case CREATE_DIALOG: - if(data != null && value instanceof Map) { - Map config = (Map)value; - String target = MapHelper.getString(config, "target", "none"); - - switch(target) { - case "meta": - Map metadata = new HashMap<>(); - List> sections = MapHelper.getList(config, "sections"); - - if(sections != null && data.length == sections.size()) { - for(int i = 0; i < sections.size(); i++) { - Map section = sections.get(i); - String key = MapHelper.getString(section, "input.key"); - - if(key != null) { - String text = String.valueOf(data[i]); - - // Get rid of text if player is currently muted - if(player.isMuted() && MapHelper.getBoolean(section, "input.sanitize")) { - text = text.replaceAll(".", "*"); - } - - metadata.put(key, text); - } else if(MapHelper.getBoolean(section, "input.mod")) { - List 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; - } - } - break; - case CHANGE: - zone.updateBlock(x, y, layer, item, mod == 0 ? 1 : 0, player); - break; - case CONTAINER: - if(metaBlock != null) { - Map metadata = metaBlock.getMetadata(); - String specialItem = MapHelper.getString(metadata, "$"); - - if(specialItem != null) { - String dungeonId = MapHelper.getString(metadata, "@"); - - if(dungeonId != null && item.hasUse(ItemUseType.FIELDABLE) && zone.isDungeonIntact(dungeonId)) { - player.notify("This container is secured by protectors in the area."); - break; - } - - if(specialItem.equals("?")) { - Loot loot = GameServer.getInstance().getLootManager().getRandomLoot(player, item.getLootCategories()); - - if(loot == null) { - player.notify("No eligible loot could be found for this container."); - } else { - metadata.remove("$"); - player.awardLoot(loot, item.getLootGraphic()); - player.getStatistics().trackContainerLooted(item); - } - } else { - player.notify("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.getStatistics().trackDiscovery(item); - 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 - Map metadata = metaBlock.getMetadata(); - List> positions = MapHelper.getList(metadata, ">", Collections.emptyList()); - zone.updateBlock(x, y, layer, item, mod % 2 == 0 ? mod + 1 : mod - 1, player, metadata); - - for(List 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; + if(interaction != null) { + interaction.interact(zone, player, x, y, layer, item, mod, metaBlock, config, data); } - } + }); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/util/MathUtils.java b/gameserver/src/main/java/brainwine/gameserver/util/MathUtils.java index f7d68e78..508c3560 100644 --- a/gameserver/src/main/java/brainwine/gameserver/util/MathUtils.java +++ b/gameserver/src/main/java/brainwine/gameserver/util/MathUtils.java @@ -34,7 +34,11 @@ public static double clamp01(double value) { return clamp(value, 0.0F, 1.0F); } + public static double distance(double x, double y, double x2, double y2) { + return Math.hypot(x - x2, y - y2); + } + public static boolean inRange(double x, double y, double x2, double y2, double range) { - return Math.hypot(x - x2, y - y2) <= range; + return distance(x, y, x2, y2) <= range; } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Block.java b/gameserver/src/main/java/brainwine/gameserver/zone/Block.java index 5ca3c8cf..aa71fbcc 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Block.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Block.java @@ -4,40 +4,35 @@ import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.item.Layer; -/** - * TODO store block owners. - */ public class Block { private Item baseItem; private Item backItem; - private int backMod; + private byte backMod; private Item frontItem; - private int frontMod; + private byte frontMod; private Item liquidItem; - private int liquidMod; + private byte liquidMod; + private short ownerHash; public Block() { - this(0, 0, 0, 0, 0, 0, 0); + this(0, 0, 0, 0, 0, 0, 0, 0); } public Block(int base, int back, int front) { - this(base & 15, back & 65535, back >> 16 & 31, front & 65535, front >> 16 & 31, base >> 8 & 255, base >> 16 & 31); + this(base & 15, back & 65535, back >> 16 & 31, front & 65535, front >> 16 & 31, base >> 8 & 255, base >> 16 & 31, front >> 21 & 2047); } - public Block(int baseItem, int backItem, int backMod, int frontItem, int frontMod, int liquidItem, int liquidMod) { + public Block(int baseItem, int backItem, int backMod, int frontItem, int frontMod, int liquidItem, int liquidMod, int ownerHash) { this(ItemRegistry.getItem(baseItem), ItemRegistry.getItem(backItem), backMod, - ItemRegistry.getItem(frontItem), frontMod, ItemRegistry.getItem(liquidItem), liquidMod); + ItemRegistry.getItem(frontItem), frontMod, ItemRegistry.getItem(liquidItem), liquidMod, ownerHash); } - public Block(Item baseItem, Item backItem, int backMod, Item frontItem, int frontMod, Item liquidItem, int liquidMod) { - this.baseItem = baseItem; - this.backItem = backItem; - this.backMod = backMod; - this.frontItem = frontItem; - this.frontMod = frontMod; - this.liquidItem = liquidItem; - this.liquidMod = liquidMod; + public Block(Item baseItem, Item backItem, int backMod, Item frontItem, int frontMod, Item liquidItem, int liquidMod, int ownerHash) { + updateLayer(Layer.BASE, baseItem, 0, ownerHash); + updateLayer(Layer.BACK, backItem, backMod, ownerHash); + updateLayer(Layer.FRONT, frontItem, frontMod, ownerHash); + updateLayer(Layer.LIQUID, liquidItem, liquidMod, ownerHash); } public void updateLayer(Layer layer, int item) { @@ -45,7 +40,11 @@ public void updateLayer(Layer layer, int item) { } public void updateLayer(Layer layer, int item, int mod) { - updateLayer(layer, ItemRegistry.getItem(item), mod); + updateLayer(layer, item, mod, 0); + } + + public void updateLayer(Layer layer, int item, int mod, int owner) { + updateLayer(layer, ItemRegistry.getItem(item), mod, owner); } public void updateLayer(Layer layer, Item item) { @@ -53,21 +52,26 @@ public void updateLayer(Layer layer, Item item) { } public void updateLayer(Layer layer, Item item, int mod) { + updateLayer(layer, item, mod, 0); + } + + public void updateLayer(Layer layer, Item item, int mod, int owner) { switch(layer) { case BASE: baseItem = item; break; case BACK: backItem = item; - backMod = mod; + backMod = (byte)(mod & 31); break; case FRONT: frontItem = item; - frontMod = mod; + frontMod = (byte)(mod & 31); + ownerHash = (short)(item.isAir() ? 0 : owner & 2047); break; case LIQUID: liquidItem = item; - liquidMod = mod; + liquidMod = (byte)(mod & 31); break; default: break; @@ -115,13 +119,13 @@ public Item getItem(Layer layer) { public void setMod(Layer layer, int mod) { switch(layer) { case BACK: - backMod = mod; + backMod = (byte)(mod & 31); break; case FRONT: - frontMod = mod; + frontMod = (byte)(mod & 31); break; case LIQUID: - liquidMod = mod; + liquidMod = (byte)(mod & 31); break; default: break; @@ -170,7 +174,7 @@ public int getFrontMod() { } public int getFront() { - return frontItem.getCode() | ((frontMod & 31) << 16); + return frontItem.getCode() | ((ownerHash & 2047) << 21) | ((frontMod & 31) << 16); } public Item getLiquidItem() { @@ -180,4 +184,12 @@ public Item getLiquidItem() { public int getLiquidMod() { return liquidMod; } + + public boolean isNatural() { + return ownerHash == 0; + } + + public int getOwnerHash() { + return ownerHash; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java b/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java index f16ae052..b2e40ae6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java @@ -2,6 +2,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.function.Function; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -13,6 +14,9 @@ import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; +/** + * I hate this class and everything in it. + */ @JsonInclude(Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class MetaBlock { @@ -83,6 +87,60 @@ public Item getItem() { return item; } + public void setProperty(String key, Object value) { + metadata.put(key, value); + } + + public void removeProperty(String key) { + metadata.remove(key); + } + + public boolean hasProperty(String key) { + return metadata.containsKey(key); + } + + public Object getProperty(String key) { + return metadata.get(key); + } + + public int getIntProperty(String key) { + return tryParse(key, Integer::parseInt, 0); + } + + public float getFloatProperty(String key) { + return tryParse(key, Float::parseFloat, 0.0f); + } + + public boolean getBooleanProperty(String key) { + return Boolean.parseBoolean(String.valueOf(getProperty(key))); + } + + public String getStringProperty(String key) { + Object value = metadata.get(key); + return value != null && value instanceof String ? (String)value : null; + } + + /** + * Generic function for parsing a number from a string. + */ + private T tryParse(String key, Function parseFunction, T def) { + Object value = metadata.get(key); + + if(value == null) { + return def; + } + + T result = def; + + try { + result = parseFunction.apply(String.valueOf(value)); + } catch(NumberFormatException e) { + // Discard silently + } + + return result; + } + public void setMetadata(Map metadata) { this.metadata = metadata; } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 97cedf59..f8cd9940 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -2,7 +2,6 @@ import java.io.File; import java.time.OffsetDateTime; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -11,7 +10,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Queue; import java.util.Random; import java.util.Set; import java.util.UUID; @@ -24,11 +22,14 @@ import com.fasterxml.jackson.annotation.JsonValue; import brainwine.gameserver.GameServer; +import brainwine.gameserver.Timer; import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.entity.player.ChatType; import brainwine.gameserver.entity.player.NotificationType; import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.DamageType; +import brainwine.gameserver.item.Fieldability; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.item.ItemUseType; @@ -41,6 +42,7 @@ import brainwine.gameserver.server.messages.BlockMetaMessage; import brainwine.gameserver.server.messages.ChatMessage; import brainwine.gameserver.server.messages.ConfigurationMessage; +import brainwine.gameserver.server.messages.EffectMessage; import brainwine.gameserver.server.messages.LightMessage; import brainwine.gameserver.server.messages.ZoneExploredMessage; import brainwine.gameserver.server.messages.ZoneStatusMessage; @@ -76,8 +78,8 @@ public class Zone { private final WeatherManager weatherManager = new WeatherManager(); private final EntityManager entityManager = new EntityManager(this); private final LiquidManager liquidManager = new LiquidManager(this); - private final Queue digQueue = new ArrayDeque<>(); private final List blockChanges = new ArrayList<>(); + private final List> blockTimers = new ArrayList<>(); private final Set pendingSunlight = new HashSet<>(); private final Map dungeons = new HashMap<>(); private final Map metaBlocks = new HashMap<>(); @@ -143,19 +145,11 @@ public void tick(float deltaTime) { } } - if(!digQueue.isEmpty()) { - DugBlock dugBlock = digQueue.peek(); - - if(now >= dugBlock.getTime()) { - digQueue.poll(); - int x = dugBlock.getX(); - int y = dugBlock.getY(); - Block block = getBlock(x, y); - - if(block != null && block.getFrontItem().hasId("ground/earth-dug")) { - updateBlock(x, y, Layer.FRONT, dugBlock.getItem(), dugBlock.getMod()); - } - } + // Process block timers + if(!blockTimers.isEmpty()) { + List> readyTimers = blockTimers.stream().filter(timer -> now >= timer.getTime()).collect(Collectors.toList()); + blockTimers.removeAll(readyTimers); + readyTimers.forEach(Timer::process); } // Send block changes to players who they are relevant to @@ -299,6 +293,123 @@ private List raycast(int x1, int y1, int x2, int y2, boolean path, boo return all ? coords : null; } + public void explode(int x, int y, float radius, Entity cause, String effect) { + explode(x, y, radius, cause, false, 0, null, effect); + } + + public void explode(int x, int y, float radius, Entity cause, boolean destructive, float damage, DamageType damageType, String effect) { + // Do nothing if the chunk at the target location isn't loaded + if(!isChunkLoaded(x, y)) { + return; + } + + sendMessageToChunk(new EffectMessage(x + 0.5f, y + 0.5f, effect, radius), getChunk(x, y)); + Player player = cause instanceof Player ? (Player)cause : null; + + // Try to destroy the block at the source of the explosion + if(getBlock(x, y).getFrontItem().getFieldability() == Fieldability.FALSE) { + updateBlock(x, y, Layer.FRONT, 0); + + if(destructive && !isBlockProtected(x, y, player)) { + updateBlock(x, y, Layer.BACK, 0); + } + } + + // Destroy blocks within range if the explosion is destructive + if(destructive) { + int rayCount = (int)Math.ceil(radius * 8); + List> rays = new ArrayList<>(); + List affectedBlocks = new ArrayList<>(); + Set processed = new HashSet<>(); + + // Determine the outer points of the blast circle and cast rays to them + for(int i = 0; i < rayCount; i++) { + float rayDistance = (float)(radius * (Math.random() * 0.4F + 0.8F)); + float angle = (float)Math.toRadians(i * (360.0F / rayCount)); + int targetX = (int)(x + rayDistance * Math.sin(angle)); + int targetY = (int)(y + rayDistance * Math.cos(angle)); + rays.add(raycast(x, y, targetX, targetY, true, true, false)); + } + + // Fetch list of field blocks that are within range of the explosion (drastically speeds up the protection check) + Collection fieldBlocksInRange = fieldBlocks.values().stream() + .filter(metaBlock -> MathUtils.inRange(x, y, metaBlock.getX(), metaBlock.getY(), metaBlock.getItem().getField() + radius * 2)) + .collect(Collectors.toList()); + + // Determine which blocks to destroy by figuring out where each ray should stop + for(List ray : rays) { + for(Vector2i position : ray) { + int positionX = position.getX(); + int positionY = position.getY(); + int index = positionY * width + positionX; + + // Skip if block has been processed + if(processed.contains(index)) { + continue; + } + + // Skip if not in bounds + if(!areCoordinatesInBounds(positionX, positionY)) { + break; + } + + Item frontItem = getBlock(positionX, positionY).getFrontItem(); + double distance = MathUtils.distance(x, y, positionX, positionY); + double power = radius - distance; + + // Do not destroy block if it invulnerable or too tough + if(!frontItem.isAir() && (frontItem.isInvulnerable() || frontItem.getToughness() >= power)) { + break; + } + + // Do not destroy block if it is protected + if(isBlockProtected(positionX, positionY, player, fieldBlocksInRange) || frontItem.hasField()) { + // Keep following this ray if the block isn't occupied + if(!frontItem.isWhole()) { + continue; + } + + break; + } + + affectedBlocks.add(position); + processed.add(index); + } + } + + // Sort affected blocks by their distance from the explosion center + affectedBlocks.sort((a, b) -> { + double distanceA = MathUtils.distance(x, y, a.getX(), a.getY()); + double distanceB = MathUtils.distance(x, y, b.getX(), b.getY()); + return distanceA > distanceB ? 1 : distanceB > distanceA ? -1 : 0; + }); + + // Destroy affected blocks + for(Vector2i position : affectedBlocks) { + updateBlock(position.getX(), position.getY(), Layer.FRONT, 0); + updateBlock(position.getX(), position.getY(), Layer.BACK, 0); + } + } + + // Fetch list of nearby entities + List nearbyEntities = getEntitiesInRange(x, y, radius); + + // Damage nearby entities based on their distance from the explosion + for(Entity entity : nearbyEntities) { + // Cast a ray from the explosion to the entity and damage it if it reaches it + if(entity.canSee(x, y)) { + double distance = MathUtils.distance(x, y, entity.getX(), entity.getY()); + entity.damage((float)(damage - distance), player); + } + + // TODO generic entity damaging is not very intuitive and needs to be worked on. + } + } + + public boolean isBlockNatural(int x, int y) { + return areCoordinatesInBounds(x, y) && getBlock(x, y).isNatural(); + } + public boolean isBlockSolid(int x, int y) { return isBlockSolid(x, y, true); } @@ -357,7 +468,11 @@ public boolean isBlockProtected(int x, int y) { } public boolean isBlockProtected(int x, int y, Player from) { - for(MetaBlock fieldBlock : fieldBlocks.values()) { + return isBlockProtected(x, y, from, fieldBlocks.values()); + } + + public boolean isBlockProtected(int x, int y, Player from, Collection fieldBlocks) { + for(MetaBlock fieldBlock : fieldBlocks) { Item item = fieldBlock.getItem(); int fX = fieldBlock.getX(); int fY = fieldBlock.getY(); @@ -405,7 +520,7 @@ public Prefab createPrefabFromSection(String name, int x, int y, int width, int 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()); + blocks[index] = new Block(block.getBaseItem(), block.getBackItem(), block.getBackMod(), block.getFrontItem(), block.getFrontMod(), block.getLiquidItem(), block.getLiquidMod(), 0); MetaBlock metaBlock = metaBlocks.get(getBlockIndex(x + i, j + y)); if(metaBlock != null) { @@ -655,14 +770,44 @@ public boolean isDungeonIntact(String id) { return dungeons.containsKey(id); } - public void digBlock(int x, int y) { + public boolean digBlock(int x, int y) { if(!areCoordinatesInBounds(x, y)) { - return; + return false; } Block block = getBlock(x, y); - digQueue.add(new DugBlock(x, y, block.getFrontItem(), block.getFrontMod(), System.currentTimeMillis() + 10000)); + Item item = block.getFrontItem(); + + if(!item.isDiggable()) { + return !item.isWhole(); + } + + int mod = block.getFrontMod(); updateBlock(x, y, Layer.FRONT, "ground/earth-dug"); + addBlockTimer(x, y, 10000, () -> { + if(block.getFrontItem().hasId("ground/earth-dug")) { + updateBlock(x, y, Layer.FRONT, item, mod); + } + }); + + return true; + } + + public void addBlockTimer(int x, int y, long delay, Runnable task) { + removeBlockTimer(x, y); + blockTimers.add(new Timer<>(getBlockIndex(x, y), delay, task)); + } + + public void removeBlockTimer(int x, int y) { + blockTimers.removeIf(timer -> timer.getKey() == getBlockIndex(x, y)); + } + + public void processBlockTimer(int x, int y) { + Timer timer = blockTimers.stream().filter(t -> t.getKey() == getBlockIndex(x, y)).findFirst().orElse(null); + + if(timer != null) { + timer.process(true); + } } public void updateBlock(int x, int y, Layer layer, int item) { @@ -715,7 +860,7 @@ public void updateBlock(int x, int y, Layer layer, Item item, int mod, Player ow } Chunk chunk = getChunk(x, y); - chunk.getBlock(x, y).updateLayer(layer, item, mod); + chunk.getBlock(x, y).updateLayer(layer, item, mod, owner == null ? 0 : owner.getBlockHash()); // TODO owner hash should get updated on place only!! chunk.setModified(true); // Queue block update if there are players in this zone. @@ -739,6 +884,7 @@ public void updateBlock(int x, int y, Layer layer, Item item, int mod, Player ow removeMetaBlock(x, y); } + removeBlockTimer(x, y); entityManager.trySpawnBlockEntity(x, y); if(item.isWhole() && y < sunlight[x]) { @@ -1165,6 +1311,10 @@ public boolean exploreArea(int x, int y, Player explorer) { return chunksExplored[chunkIndex] = true; } + public boolean isAreaExplored(int x, int y) { + return areCoordinatesInBounds(x, y) && chunksExplored[getChunkIndex(x, y)]; + } + public File getDirectory() { return new File("zones", documentId); } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java index 9c8e1eb0..4398e731 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java @@ -178,8 +178,11 @@ public Zone getZoneByName(String name) { } public Zone getRandomZone() { - List zones = new ArrayList<>(); - zones.addAll(getZones()); + return getRandomZone(null); + } + + public Zone getRandomZone(Predicate predicate) { + List zones = searchZones(predicate); return zones.get((int)(Math.random() * zones.size())); } From ac1069c3a9a4c043f7d832179bc1c993bc9b4d81 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 17 Feb 2024 22:05:25 +0100 Subject: [PATCH 004/176] Change default deep biome size to 1200x1000 --- .../gameserver/command/commands/GenerateZoneCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/GenerateZoneCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/commands/GenerateZoneCommand.java index f2f66eea..6c316aab 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/GenerateZoneCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/commands/GenerateZoneCommand.java @@ -19,8 +19,8 @@ public class GenerateZoneCommand extends Command { @Override public void execute(CommandExecutor executor, String[] args) { Biome biome = Biome.getRandomBiome(); - int width = 2000; - int height = 600; + int width = biome == Biome.DEEP ? 1200 : 2000; + int height = biome == Biome.DEEP ? 1000 : 600; int seed = (int)(Math.random() * Integer.MAX_VALUE); if(args.length > 0 && args.length < 2) { From c6779f0f61827d23708e4201fa2c4bf4326eb5d8 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 17 Feb 2024 22:10:59 +0100 Subject: [PATCH 005/176] Place spawn buildings closer to the top in `fill` terrain types --- .../gameserver/zone/gen/tasks/StructureGeneratorTask.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java index f484ffef..c4aa59c5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java @@ -152,7 +152,8 @@ private void placeRandomSpawnBuilding(GeneratorContext ctx, int x) { Prefab spawnBuilding = spawnBuildings.next(ctx.getRandom()); if(filled) { - int y = ctx.getHeight() / 8 + ctx.nextInt(Math.max(1, ctx.nextInt(ctx.getHeight() / 8))); + int min = ctx.getHeight() / 32; + int y = min + ctx.nextInt(Math.max(1, ctx.nextInt(min))); ctx.placePrefab(spawnBuilding, x, y); } else { ctx.placePrefabSurface(spawnBuilding, x); From 3bba0893b5d43d469d4844eed0a3d3c819c79e22 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 17 Feb 2024 22:33:20 +0100 Subject: [PATCH 006/176] Add layer separator zone generator feature --- .../gameserver/zone/gen/GeneratorConfig.java | 6 +++ .../zone/gen/models/LayerSeparator.java | 51 +++++++++++++++++++ .../zone/gen/tasks/CaveGeneratorTask.java | 7 +-- .../zone/gen/tasks/TerrainGeneratorTask.java | 23 +++++++++ .../resources/defaults/generators/deep.json | 7 +++ .../resources/defaults/generators/hell.json | 7 +++ 6 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/zone/gen/models/LayerSeparator.java diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/GeneratorConfig.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/GeneratorConfig.java index eafb3f13..9fbbd197 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/GeneratorConfig.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/GeneratorConfig.java @@ -15,6 +15,7 @@ import brainwine.gameserver.zone.gen.caves.CaveDecorator; import brainwine.gameserver.zone.gen.caves.CaveType; import brainwine.gameserver.zone.gen.models.Deposit; +import brainwine.gameserver.zone.gen.models.LayerSeparator; import brainwine.gameserver.zone.gen.models.OreDeposit; import brainwine.gameserver.zone.gen.models.SpecialStructure; import brainwine.gameserver.zone.gen.models.StoneType; @@ -33,6 +34,7 @@ public class GeneratorConfig { private double dungeonChance = 0.25; private double backgroundAccentChance = 0.033; private double backgroundDrawingChance = 0.001; + private LayerSeparator layerSeparator; private WeightedMap stoneTypes = new WeightedMap<>(); private WeightedMap spawnBuildings = new WeightedMap<>(); private WeightedMap dungeons = new WeightedMap<>(); @@ -86,6 +88,10 @@ public double getBackgroundDrawingChance() { return backgroundDrawingChance; } + public LayerSeparator getLayerSeparator() { + return layerSeparator; + } + @JsonSetter(value = "stone_types", nulls = Nulls.SKIP) public WeightedMap getStoneTypes() { return stoneTypes; diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/models/LayerSeparator.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/models/LayerSeparator.java new file mode 100644 index 00000000..8bc144a6 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/models/LayerSeparator.java @@ -0,0 +1,51 @@ +package brainwine.gameserver.zone.gen.models; + +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 LayerSeparator { + + @JsonProperty("item") + private Item item; + + @JsonProperty("min_thickness") + private int minThickness = 3; + + @JsonProperty("max_thickness") + private int maxThickness = 6; + + @JsonProperty("min_amplitude") + private double minAmplitude = 20; + + @JsonProperty("max_amplitude") + private double maxAimplitude = 20; + + @JsonCreator + private LayerSeparator(@JsonProperty(value = "item", required = true) Item item) { + this.item = item; + } + + public Item getItem() { + return item; + } + + public int getMinThickness() { + return minThickness; + } + + public int getMaxThickness() { + return maxThickness; + } + + public double getMinAmplitude() { + return minAmplitude; + } + + public double getMaxAmplitude() { + return maxAimplitude; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/CaveGeneratorTask.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/CaveGeneratorTask.java index 40f5868a..3921ee75 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/CaveGeneratorTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/CaveGeneratorTask.java @@ -99,11 +99,11 @@ public void generate(GeneratorContext ctx) { // Generate a cave wall with a thickness depending on the size of the cave if(asteroids || stoneType != StoneType.DEFAULT) { ctx.updateBlock(x, y, Layer.BASE, stoneType.getBaseItem()); - int checkDistance = asteroids? 5 : 3; + int checkDistance = asteroids ? 5 : 3; for(int i = x - checkDistance; i <= x + checkDistance; i++) { for(int j = y - checkDistance; j <= y + checkDistance; j++) { - if(ctx.inBounds(i, j) && !cells[i][j]) { + if((asteroids ? ctx.isAir(i, j, Layer.FRONT) : ctx.isEarthy(i, j)) && !cells[i][j]) { double maxDistance = asteroids ? 4.5 + ctx.nextDouble() - 1 : MathUtils.clamp(cave.getSize() / 16.0, 1.8, checkDistance) + (ctx.nextDouble() - 0.5); double distance = Math.hypot(i - x, j - y); @@ -169,7 +169,8 @@ public boolean[][] generateCells(GeneratorContext ctx, double cellRate, int smoo for(int x = 0; x < width; x++) { for(int y = 0; y < height; y++) { - if((y >= ctx.getSurface(x) + ctx.nextInt(3)) && ctx.nextDouble() <= cellRate) { + if((terrainType == TerrainType.ASTEROIDS ? ctx.isAir(x, y, Layer.FRONT) : ctx.isEarthy(x, y)) + && (y >= ctx.getSurface(x) + ctx.nextInt(3)) && ctx.nextDouble() <= cellRate) { cells[x][y] = true; } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/TerrainGeneratorTask.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/TerrainGeneratorTask.java index 26783f86..21e4dc27 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/TerrainGeneratorTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/TerrainGeneratorTask.java @@ -1,10 +1,12 @@ package brainwine.gameserver.zone.gen.tasks; +import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; import brainwine.gameserver.util.SimplexNoise; import brainwine.gameserver.util.WeightedMap; import brainwine.gameserver.zone.gen.GeneratorConfig; import brainwine.gameserver.zone.gen.GeneratorContext; +import brainwine.gameserver.zone.gen.models.LayerSeparator; import brainwine.gameserver.zone.gen.models.TerrainType; import brainwine.gameserver.zone.gen.surface.SurfaceRegion; import brainwine.gameserver.zone.gen.surface.SurfaceRegionType; @@ -14,6 +16,7 @@ public class TerrainGeneratorTask implements GeneratorTask { private final TerrainType type; private final double minAmplitude; private final double maxAmplitude; + private final LayerSeparator layerSeparator; private final int surfaceRegionSize; private final WeightedMap surfaceRegionTypes; @@ -21,6 +24,7 @@ public TerrainGeneratorTask(GeneratorConfig config) { type = config.getTerrainType(); minAmplitude = config.getMinAmplitude(); maxAmplitude = config.getMaxAmplitude(); + layerSeparator = config.getLayerSeparator(); surfaceRegionSize = config.getSurfaceRegionSize(); surfaceRegionTypes = config.getSurfaceRegionTypes(); } @@ -72,5 +76,24 @@ public void generate(GeneratorContext ctx) { ctx.updateBlock(x, y, Layer.BASE, "base/earth"); } } + + // Generate layer separators + if(layerSeparator != null) { + Item item = layerSeparator.getItem(); + int minThickness = layerSeparator.getMinThickness(); + int maxThickness = layerSeparator.getMaxThickness(); + double amplitude = ctx.nextDouble() * (layerSeparator.getMaxAmplitude() - layerSeparator.getMinAmplitude()) + layerSeparator.getMinAmplitude(); + + for(int depth : ctx.getZone().getDepths()) { + for(int x = 0; x < width; x++) { + int start = (int)(SimplexNoise.noise2(ctx.getSeed(), x / 256.0, 0, 7) * amplitude) + depth - maxThickness / 2; + int size = ctx.nextInt(maxThickness - minThickness) + minThickness; + + for(int y = start; y < start + size; y++) { + ctx.updateBlock(x, y, item.getLayer(), item); + } + } + } + } } } diff --git a/gameserver/src/main/resources/defaults/generators/deep.json b/gameserver/src/main/resources/defaults/generators/deep.json index a218fd0e..f23768fe 100644 --- a/gameserver/src/main/resources/defaults/generators/deep.json +++ b/gameserver/src/main/resources/defaults/generators/deep.json @@ -4,6 +4,13 @@ "dungeon_chance": 0.4, "background_accent_chance": 0.033, "background_drawing_chance": 0.001, + "layer_separator": { + "item": "ground/blackrock", + "min_thickness": 3, + "max_thickness": 6, + "min_amplitude": 20, + "max_amplitude": 20 + }, "stone_types": { "default": 17, "limestone": 4 diff --git a/gameserver/src/main/resources/defaults/generators/hell.json b/gameserver/src/main/resources/defaults/generators/hell.json index c577dc75..67c5ddf5 100644 --- a/gameserver/src/main/resources/defaults/generators/hell.json +++ b/gameserver/src/main/resources/defaults/generators/hell.json @@ -7,6 +7,13 @@ "dungeon_chance": 0.375, "background_accent_chance": 0.033, "background_drawing_chance": 0.001, + "layer_separator": { + "item": "ground/blackrock", + "min_thickness": 3, + "max_thickness": 6, + "min_amplitude": 20, + "max_amplitude": 20 + }, "stone_types": { "default": 1 }, From 2b0900b3504bee8ea418f3d18930aa6f83c8c0d8 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 17 Feb 2024 22:39:04 +0100 Subject: [PATCH 007/176] Fix space biome not configuring properly --- deepworld-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepworld-config b/deepworld-config index 827c1fac..6052c539 160000 --- a/deepworld-config +++ b/deepworld-config @@ -1 +1 @@ -Subproject commit 827c1fac075ed2a9d92f56696f36101d54485109 +Subproject commit 6052c53944e3d4469819e725c59914ea136b2007 From 887e235d9da83f50e48abd9827bae5fca7c8f905 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 17 Feb 2024 23:22:03 +0100 Subject: [PATCH 008/176] Fix potential mechanical sign crash --- .../gameserver/item/interactions/SwitchInteraction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java index 3fb9ff53..8adf44e5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java @@ -164,7 +164,7 @@ private void switchExploder(Zone zone, Entity entity, MetaBlock metaBlock) { private void switchSign(Zone zone, Entity entity, MetaBlock metaBlock, MetaBlock switchMeta) { String message = switchMeta.hasProperty("m") ? switchMeta.getStringProperty("m").trim() : ""; - boolean lock = metaBlock.getStringProperty("lock").equalsIgnoreCase("yes"); + boolean lock = metaBlock.hasProperty("lock") && metaBlock.getStringProperty("lock").equalsIgnoreCase("yes"); Item item = metaBlock.getItem(); // Check and update lock status From 310174864ee87d6be8e141375f52648cf27435d6 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 17 Feb 2024 23:25:37 +0100 Subject: [PATCH 009/176] Move behavior to NPC package --- .../brainwine/gameserver/entity/npc/Npc.java | 2 +- .../{ => entity/npc}/behavior/Behavior.java | 40 +++++++++---------- .../npc}/behavior/CompositeBehavior.java | 2 +- .../npc}/behavior/SelectorBehavior.java | 2 +- .../npc}/behavior/SequenceBehavior.java | 2 +- .../behavior/composed/CrawlerBehavior.java | 14 +++---- .../behavior/composed/DiggerBehavior.java | 14 +++---- .../npc}/behavior/composed/FlyerBehavior.java | 10 ++--- .../behavior/composed/WalkerBehavior.java | 12 +++--- .../npc}/behavior/parts/ClimbBehavior.java | 4 +- .../npc}/behavior/parts/DigBehavior.java | 4 +- .../parts/EruptionAttackBehavior.java | 4 +- .../npc}/behavior/parts/FallBehavior.java | 4 +- .../npc}/behavior/parts/FlyBehavior.java | 4 +- .../behavior/parts/FlyTowardBehavior.java | 2 +- .../npc}/behavior/parts/FollowBehavior.java | 4 +- .../npc}/behavior/parts/IdleBehavior.java | 4 +- .../parts/RandomlyTargetBehavior.java | 4 +- .../npc}/behavior/parts/ReporterBehavior.java | 4 +- .../npc}/behavior/parts/ShielderBehavior.java | 4 +- .../behavior/parts/SpawnAttackBehavior.java | 4 +- .../npc}/behavior/parts/TurnBehavior.java | 4 +- .../npc}/behavior/parts/UnblockBehavior.java | 4 +- .../npc}/behavior/parts/WalkBehavior.java | 4 +- 24 files changed, 78 insertions(+), 78 deletions(-) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/Behavior.java (61%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/CompositeBehavior.java (97%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/SelectorBehavior.java (91%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/SequenceBehavior.java (97%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/composed/CrawlerBehavior.java (67%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/composed/DiggerBehavior.java (66%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/composed/FlyerBehavior.java (71%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/composed/WalkerBehavior.java (68%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/parts/ClimbBehavior.java (90%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/parts/DigBehavior.java (86%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/parts/EruptionAttackBehavior.java (96%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/parts/FallBehavior.java (87%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/parts/FlyBehavior.java (94%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/parts/FlyTowardBehavior.java (95%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/parts/FollowBehavior.java (84%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/parts/IdleBehavior.java (96%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/parts/RandomlyTargetBehavior.java (93%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/parts/ReporterBehavior.java (80%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/parts/ShielderBehavior.java (96%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/parts/SpawnAttackBehavior.java (96%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/parts/TurnBehavior.java (85%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/parts/UnblockBehavior.java (90%) rename gameserver/src/main/java/brainwine/gameserver/{ => entity/npc}/behavior/parts/WalkBehavior.java (89%) diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java index bc75fb3d..4046b13a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java @@ -9,12 +9,12 @@ import java.util.Map.Entry; import java.util.concurrent.ThreadLocalRandom; -import brainwine.gameserver.behavior.SequenceBehavior; import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.EntityConfig; import brainwine.gameserver.entity.EntityLoot; import brainwine.gameserver.entity.EntityRegistry; import brainwine.gameserver.entity.FacingDirection; +import brainwine.gameserver.entity.npc.behavior.SequenceBehavior; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/Behavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/Behavior.java similarity index 61% rename from gameserver/src/main/java/brainwine/gameserver/behavior/Behavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/Behavior.java index 610f801b..36fe0cd8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/Behavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/Behavior.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.behavior; +package brainwine.gameserver.entity.npc.behavior; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; @@ -7,26 +7,26 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo.As; import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; -import brainwine.gameserver.behavior.composed.CrawlerBehavior; -import brainwine.gameserver.behavior.composed.DiggerBehavior; -import brainwine.gameserver.behavior.composed.FlyerBehavior; -import brainwine.gameserver.behavior.composed.WalkerBehavior; -import brainwine.gameserver.behavior.parts.ClimbBehavior; -import brainwine.gameserver.behavior.parts.DigBehavior; -import brainwine.gameserver.behavior.parts.EruptionAttackBehavior; -import brainwine.gameserver.behavior.parts.FallBehavior; -import brainwine.gameserver.behavior.parts.FlyBehavior; -import brainwine.gameserver.behavior.parts.FlyTowardBehavior; -import brainwine.gameserver.behavior.parts.FollowBehavior; -import brainwine.gameserver.behavior.parts.IdleBehavior; -import brainwine.gameserver.behavior.parts.RandomlyTargetBehavior; -import brainwine.gameserver.behavior.parts.ReporterBehavior; -import brainwine.gameserver.behavior.parts.ShielderBehavior; -import brainwine.gameserver.behavior.parts.SpawnAttackBehavior; -import brainwine.gameserver.behavior.parts.TurnBehavior; -import brainwine.gameserver.behavior.parts.UnblockBehavior; -import brainwine.gameserver.behavior.parts.WalkBehavior; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.composed.CrawlerBehavior; +import brainwine.gameserver.entity.npc.behavior.composed.DiggerBehavior; +import brainwine.gameserver.entity.npc.behavior.composed.FlyerBehavior; +import brainwine.gameserver.entity.npc.behavior.composed.WalkerBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.ClimbBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.DigBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.EruptionAttackBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.FallBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.FlyBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.FlyTowardBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.FollowBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.RandomlyTargetBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.ReporterBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.ShielderBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.SpawnAttackBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.TurnBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.UnblockBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.WalkBehavior; /** * Heavily based on Deepworld's original "rubyhave" (ha ha very punny) behavior system. diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/CompositeBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/CompositeBehavior.java similarity index 97% rename from gameserver/src/main/java/brainwine/gameserver/behavior/CompositeBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/CompositeBehavior.java index 2f569553..ef075088 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/CompositeBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/CompositeBehavior.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.behavior; +package brainwine.gameserver.entity.npc.behavior; import static brainwine.shared.LogMarkers.SERVER_MARKER; diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/SelectorBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/SelectorBehavior.java similarity index 91% rename from gameserver/src/main/java/brainwine/gameserver/behavior/SelectorBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/SelectorBehavior.java index bf1025ac..85bbbb64 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/SelectorBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/SelectorBehavior.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.behavior; +package brainwine.gameserver.entity.npc.behavior; import java.util.Map; diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/SequenceBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/SequenceBehavior.java similarity index 97% rename from gameserver/src/main/java/brainwine/gameserver/behavior/SequenceBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/SequenceBehavior.java index 9bc5890e..0999021a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/SequenceBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/SequenceBehavior.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.behavior; +package brainwine.gameserver.entity.npc.behavior; import static brainwine.shared.LogMarkers.SERVER_MARKER; diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/composed/CrawlerBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/CrawlerBehavior.java similarity index 67% rename from gameserver/src/main/java/brainwine/gameserver/behavior/composed/CrawlerBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/CrawlerBehavior.java index 53575bca..4c2821d6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/composed/CrawlerBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/CrawlerBehavior.java @@ -1,17 +1,17 @@ -package brainwine.gameserver.behavior.composed; +package brainwine.gameserver.entity.npc.behavior.composed; import java.util.Map; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.behavior.SelectorBehavior; -import brainwine.gameserver.behavior.parts.ClimbBehavior; -import brainwine.gameserver.behavior.parts.FallBehavior; -import brainwine.gameserver.behavior.parts.IdleBehavior; -import brainwine.gameserver.behavior.parts.TurnBehavior; -import brainwine.gameserver.behavior.parts.WalkBehavior; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.SelectorBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.ClimbBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.FallBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.TurnBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.WalkBehavior; import brainwine.gameserver.util.MapHelper; public class CrawlerBehavior extends SelectorBehavior { diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/composed/DiggerBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/DiggerBehavior.java similarity index 66% rename from gameserver/src/main/java/brainwine/gameserver/behavior/composed/DiggerBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/DiggerBehavior.java index d6fb9175..bf95bf7e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/composed/DiggerBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/DiggerBehavior.java @@ -1,17 +1,17 @@ -package brainwine.gameserver.behavior.composed; +package brainwine.gameserver.entity.npc.behavior.composed; import java.util.Map; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.behavior.SelectorBehavior; -import brainwine.gameserver.behavior.parts.DigBehavior; -import brainwine.gameserver.behavior.parts.FallBehavior; -import brainwine.gameserver.behavior.parts.IdleBehavior; -import brainwine.gameserver.behavior.parts.TurnBehavior; -import brainwine.gameserver.behavior.parts.WalkBehavior; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.SelectorBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.DigBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.FallBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.TurnBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.WalkBehavior; import brainwine.gameserver.util.MapHelper; public class DiggerBehavior extends SelectorBehavior { diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/composed/FlyerBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/FlyerBehavior.java similarity index 71% rename from gameserver/src/main/java/brainwine/gameserver/behavior/composed/FlyerBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/FlyerBehavior.java index d511c6f1..1f5b60d7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/composed/FlyerBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/FlyerBehavior.java @@ -1,15 +1,15 @@ -package brainwine.gameserver.behavior.composed; +package brainwine.gameserver.entity.npc.behavior.composed; import java.util.Map; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.behavior.SelectorBehavior; -import brainwine.gameserver.behavior.parts.FlyBehavior; -import brainwine.gameserver.behavior.parts.FlyTowardBehavior; -import brainwine.gameserver.behavior.parts.IdleBehavior; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.SelectorBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.FlyBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.FlyTowardBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior; import brainwine.gameserver.util.MapHelper; public class FlyerBehavior extends SelectorBehavior { diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/composed/WalkerBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/WalkerBehavior.java similarity index 68% rename from gameserver/src/main/java/brainwine/gameserver/behavior/composed/WalkerBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/WalkerBehavior.java index 4ad4a9a7..15eb95d0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/composed/WalkerBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/WalkerBehavior.java @@ -1,16 +1,16 @@ -package brainwine.gameserver.behavior.composed; +package brainwine.gameserver.entity.npc.behavior.composed; import java.util.Map; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.behavior.SelectorBehavior; -import brainwine.gameserver.behavior.parts.FallBehavior; -import brainwine.gameserver.behavior.parts.IdleBehavior; -import brainwine.gameserver.behavior.parts.TurnBehavior; -import brainwine.gameserver.behavior.parts.WalkBehavior; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.SelectorBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.FallBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.TurnBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.WalkBehavior; import brainwine.gameserver.util.MapHelper; public class WalkerBehavior extends SelectorBehavior { diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/ClimbBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ClimbBehavior.java similarity index 90% rename from gameserver/src/main/java/brainwine/gameserver/behavior/parts/ClimbBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ClimbBehavior.java index dc86bc90..9b3c7cb1 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/ClimbBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ClimbBehavior.java @@ -1,11 +1,11 @@ -package brainwine.gameserver.behavior.parts; +package brainwine.gameserver.entity.npc.behavior.parts; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.behavior.Behavior; import brainwine.gameserver.entity.FacingDirection; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; public class ClimbBehavior extends Behavior { diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/DigBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/DigBehavior.java similarity index 86% rename from gameserver/src/main/java/brainwine/gameserver/behavior/parts/DigBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/DigBehavior.java index ab133f94..109bf246 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/DigBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/DigBehavior.java @@ -1,10 +1,10 @@ -package brainwine.gameserver.behavior.parts; +package brainwine.gameserver.entity.npc.behavior.parts; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.behavior.Behavior; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; import brainwine.gameserver.zone.Zone; public class DigBehavior extends Behavior { diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/EruptionAttackBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/EruptionAttackBehavior.java similarity index 96% rename from gameserver/src/main/java/brainwine/gameserver/behavior/parts/EruptionAttackBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/EruptionAttackBehavior.java index a21ff180..fa396978 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/EruptionAttackBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/EruptionAttackBehavior.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.behavior.parts; +package brainwine.gameserver.entity.npc.behavior.parts; import java.util.Map; @@ -6,10 +6,10 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonSetter; -import brainwine.gameserver.behavior.Behavior; import brainwine.gameserver.entity.EntityConfig; import brainwine.gameserver.entity.EntityStatus; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; import brainwine.gameserver.server.messages.EntityStatusMessage; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.Vector2i; diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/FallBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/FallBehavior.java similarity index 87% rename from gameserver/src/main/java/brainwine/gameserver/behavior/parts/FallBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/FallBehavior.java index 686f1e55..3e713eb6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/FallBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/FallBehavior.java @@ -1,11 +1,11 @@ -package brainwine.gameserver.behavior.parts; +package brainwine.gameserver.entity.npc.behavior.parts; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.behavior.Behavior; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; public class FallBehavior extends Behavior { diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/FlyBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/FlyBehavior.java similarity index 94% rename from gameserver/src/main/java/brainwine/gameserver/behavior/parts/FlyBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/FlyBehavior.java index 20676877..f8594f3e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/FlyBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/FlyBehavior.java @@ -1,12 +1,12 @@ -package brainwine.gameserver.behavior.parts; +package brainwine.gameserver.entity.npc.behavior.parts; import java.util.concurrent.ThreadLocalRandom; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.behavior.Behavior; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; import brainwine.gameserver.util.Vector2i; public class FlyBehavior extends Behavior { diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/FlyTowardBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/FlyTowardBehavior.java similarity index 95% rename from gameserver/src/main/java/brainwine/gameserver/behavior/parts/FlyTowardBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/FlyTowardBehavior.java index baa0a9de..1f60e15b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/FlyTowardBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/FlyTowardBehavior.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.behavior.parts; +package brainwine.gameserver.entity.npc.behavior.parts; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/FollowBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/FollowBehavior.java similarity index 84% rename from gameserver/src/main/java/brainwine/gameserver/behavior/parts/FollowBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/FollowBehavior.java index 087b004d..ecbee083 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/FollowBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/FollowBehavior.java @@ -1,11 +1,11 @@ -package brainwine.gameserver.behavior.parts; +package brainwine.gameserver.entity.npc.behavior.parts; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.behavior.Behavior; import brainwine.gameserver.entity.FacingDirection; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; public class FollowBehavior extends Behavior { diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/IdleBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/IdleBehavior.java similarity index 96% rename from gameserver/src/main/java/brainwine/gameserver/behavior/parts/IdleBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/IdleBehavior.java index 68112f65..d9f95979 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/IdleBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/IdleBehavior.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.behavior.parts; +package brainwine.gameserver.entity.npc.behavior.parts; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; @@ -7,9 +7,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonSetter; -import brainwine.gameserver.behavior.Behavior; import brainwine.gameserver.entity.FacingDirection; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; import brainwine.gameserver.util.Vector2i; public class IdleBehavior extends Behavior { diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/RandomlyTargetBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/RandomlyTargetBehavior.java similarity index 93% rename from gameserver/src/main/java/brainwine/gameserver/behavior/parts/RandomlyTargetBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/RandomlyTargetBehavior.java index afc982a6..0466adb4 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/RandomlyTargetBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/RandomlyTargetBehavior.java @@ -1,10 +1,10 @@ -package brainwine.gameserver.behavior.parts; +package brainwine.gameserver.entity.npc.behavior.parts; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.behavior.Behavior; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; import brainwine.gameserver.entity.player.Player; public class RandomlyTargetBehavior extends Behavior { diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/ReporterBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ReporterBehavior.java similarity index 80% rename from gameserver/src/main/java/brainwine/gameserver/behavior/parts/ReporterBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ReporterBehavior.java index c77fc7e1..a1beaf8c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/ReporterBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ReporterBehavior.java @@ -1,10 +1,10 @@ -package brainwine.gameserver.behavior.parts; +package brainwine.gameserver.entity.npc.behavior.parts; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.behavior.Behavior; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; public class ReporterBehavior extends Behavior { diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/ShielderBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ShielderBehavior.java similarity index 96% rename from gameserver/src/main/java/brainwine/gameserver/behavior/parts/ShielderBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ShielderBehavior.java index 0a0de4ed..01b2eb68 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/ShielderBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ShielderBehavior.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.behavior.parts; +package brainwine.gameserver.entity.npc.behavior.parts; import java.util.Arrays; import java.util.Collection; @@ -9,8 +9,8 @@ import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.behavior.Behavior; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; import brainwine.gameserver.util.Pair; diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/SpawnAttackBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/SpawnAttackBehavior.java similarity index 96% rename from gameserver/src/main/java/brainwine/gameserver/behavior/parts/SpawnAttackBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/SpawnAttackBehavior.java index 6650737d..4c8d615f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/SpawnAttackBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/SpawnAttackBehavior.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.behavior.parts; +package brainwine.gameserver.entity.npc.behavior.parts; import java.util.Map; @@ -6,10 +6,10 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonSetter; -import brainwine.gameserver.behavior.Behavior; import brainwine.gameserver.entity.EntityConfig; import brainwine.gameserver.entity.EntityStatus; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; import brainwine.gameserver.server.messages.EntityStatusMessage; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.Vector2i; diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/TurnBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/TurnBehavior.java similarity index 85% rename from gameserver/src/main/java/brainwine/gameserver/behavior/parts/TurnBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/TurnBehavior.java index 8ae0134c..9e2fcf05 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/TurnBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/TurnBehavior.java @@ -1,11 +1,11 @@ -package brainwine.gameserver.behavior.parts; +package brainwine.gameserver.entity.npc.behavior.parts; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.behavior.Behavior; import brainwine.gameserver.entity.FacingDirection; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; public class TurnBehavior extends Behavior { diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/UnblockBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/UnblockBehavior.java similarity index 90% rename from gameserver/src/main/java/brainwine/gameserver/behavior/parts/UnblockBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/UnblockBehavior.java index 929b0dc0..e1615b00 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/UnblockBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/UnblockBehavior.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.behavior.parts; +package brainwine.gameserver.entity.npc.behavior.parts; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; @@ -6,8 +6,8 @@ import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.behavior.Behavior; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; import brainwine.gameserver.util.Vector2i; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/WalkBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/WalkBehavior.java similarity index 89% rename from gameserver/src/main/java/brainwine/gameserver/behavior/parts/WalkBehavior.java rename to gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/WalkBehavior.java index 8e746eea..62e23dda 100644 --- a/gameserver/src/main/java/brainwine/gameserver/behavior/parts/WalkBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/WalkBehavior.java @@ -1,12 +1,12 @@ -package brainwine.gameserver.behavior.parts; +package brainwine.gameserver.entity.npc.behavior.parts; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.behavior.Behavior; import brainwine.gameserver.entity.FacingDirection; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; public class WalkBehavior extends Behavior { From 49ab4c42f46a6c346f9bdbc3368ee04ad2ee5448 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 18 Feb 2024 02:18:22 +0100 Subject: [PATCH 010/176] Abstract entity damaging --- .../brainwine/gameserver/entity/Entity.java | 40 +++++++++- .../gameserver/entity/EntityAttack.java | 41 ++++++++++ .../brainwine/gameserver/entity/npc/Npc.java | 77 +++++++------------ .../npc/behavior/parts/ShielderBehavior.java | 10 +-- .../gameserver/entity/player/Player.java | 24 +++++- .../server/requests/InventoryUseRequest.java | 5 +- .../java/brainwine/gameserver/zone/Zone.java | 7 +- 7 files changed, 139 insertions(+), 65 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/EntityAttack.java diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java index 6009cc9f..879871ea 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java @@ -1,11 +1,13 @@ package brainwine.gameserver.entity; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; @@ -23,8 +25,11 @@ public abstract class Entity { public static final float DEFAULT_HEALTH = 5; public static final float POSITION_MODIFIER = 100F; public static final int VELOCITY_MODIFIER = (int)POSITION_MODIFIER; + public static final int ATTACK_RETENTION_TIME = 2000; + public static final int ATTACK_INVINCIBLE_TIME = 333; protected final Map properties = new HashMap<>(); protected final List trackers = new ArrayList<>(); + protected final List recentAttacks = new ArrayList<>(); protected int type; protected String name; protected float health = DEFAULT_HEALTH; @@ -49,6 +54,8 @@ public Entity(Zone zone) { } public void tick(float deltaTime) { + long now = System.currentTimeMillis(); + // Update block position lastBlockX = blockX; lastBlockY = blockY; @@ -59,9 +66,12 @@ public void tick(float deltaTime) { if(lastBlockX != blockX || lastBlockY != blockY) { blockPositionChanged(); } + + // Clear expired recent attacks + recentAttacks.removeIf(attack -> now >= attack.getTime() + ATTACK_RETENTION_TIME); } - public void die(Player killer) { + public void die(Entity killer) { // Override } @@ -75,7 +85,7 @@ public void damage(float amount) { damage(amount, null); } - public void damage(float amount, Player attacker) { + public void damage(float amount, Entity attacker) { setHealth(health - amount); if(health <= 0) { @@ -85,6 +95,24 @@ public void damage(float amount, Player attacker) { lastDamagedAt = System.currentTimeMillis(); } + public void attack(Entity attacker, Item weapon, float baseDamage, DamageType damageType) { + EntityAttack attack = new EntityAttack(attacker, weapon, baseDamage, damageType); + boolean ignoreDefense = attacker != null && attacker.isPlayer() && ((Player)attacker).isGodMode(); + float attackMultiplier = attacker != null ? Math.max(0.0F, attacker.getAttackMultiplier(attack)) : 1.0F; + float defense = Math.max(0.0F, 1.0F - getDefense(attack)); + float damage = baseDamage * attackMultiplier * (ignoreDefense ? 1.0F : defense); + damage(damage, attacker); + recentAttacks.add(attack); + } + + public float getAttackMultiplier(EntityAttack attack) { + return 1.0F; // Override + } + + public float getDefense(EntityAttack attack) { + return 1.0F; // Override + } + public void blockPositionChanged() { // Check for touchplates if(zone != null && zone.isChunkLoaded(blockX, blockY)) { @@ -161,6 +189,14 @@ public List getTrackers() { return trackers; } + public boolean wasAttackedRecently(Entity entity, int delay) { + return recentAttacks.stream().filter(attack -> attack.getAttacker() == entity && System.currentTimeMillis() < attack.getTime() + delay).findFirst().isPresent(); + } + + public List getRecentAttacks() { + return Collections.unmodifiableList(recentAttacks); + } + public void setId(int id) { this.id = id; } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/EntityAttack.java b/gameserver/src/main/java/brainwine/gameserver/entity/EntityAttack.java new file mode 100644 index 00000000..70ccebfe --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/entity/EntityAttack.java @@ -0,0 +1,41 @@ +package brainwine.gameserver.entity; + +import brainwine.gameserver.item.DamageType; +import brainwine.gameserver.item.Item; + +public class EntityAttack { + + private final Entity attacker; + private final Item weapon; + private final float baseDamage; + private final DamageType damageType; + private final long time; + + public EntityAttack(Entity attacker, Item weapon, float baseDamage, DamageType damageType) { + this.attacker = attacker; + this.weapon = weapon; + this.baseDamage = baseDamage; + this.damageType = damageType; + this.time = System.currentTimeMillis(); + } + + public Entity getAttacker() { + return attacker; + } + + public Item getWeapon() { + return weapon; + } + + public float getBaseDamage() { + return baseDamage; + } + + public DamageType getDamageType() { + return damageType; + } + + public long getTime() { + return time; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java index 4046b13a..5dd30440 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java @@ -10,6 +10,7 @@ import java.util.concurrent.ThreadLocalRandom; import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.EntityAttack; import brainwine.gameserver.entity.EntityConfig; import brainwine.gameserver.entity.EntityLoot; import brainwine.gameserver.entity.EntityRegistry; @@ -20,7 +21,6 @@ import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; import brainwine.gameserver.util.MapHelper; -import brainwine.gameserver.util.Pair; import brainwine.gameserver.util.Vector2i; import brainwine.gameserver.util.WeightedMap; import brainwine.gameserver.zone.MetaBlock; @@ -28,8 +28,6 @@ public class Npc extends Entity { - public static final int ATTACK_RETENTION_TIME = 2000; - public static final int ATTACK_INVINCIBLE_TIME = 333; private final EntityConfig config; private final String typeName; private final float maxHealth; @@ -43,7 +41,6 @@ public class Npc extends Entity { private final List animations; private final SequenceBehavior behaviorTree; private final Map activeDefenses = new HashMap<>(); - private final Map> recentAttacks = new HashMap<>(); private final List children = new ArrayList<>(); private float speed; private int moveX; @@ -124,9 +121,6 @@ public void tick(float deltaTime) { super.tick(deltaTime); long now = System.currentTimeMillis(); - // Clear expired recent attacks - recentAttacks.values().removeIf(attack -> now >= attack.getLast() + ATTACK_RETENTION_TIME); - // Tick behavior when it is ready if(now >= lastBehavedAt + (int)(1000 / speed)) { lastBehavedAt = now; @@ -148,27 +142,31 @@ public void tick(float deltaTime) { } @Override - public void die(Player killer) { + public void die(Entity killer) { // Grant loot & track kill - if(!artificial && killer != null) { + if(!artificial && killer != null && killer.isPlayer()) { + Player player = (Player)killer; + if(!isPlayerPlaced()) { // Track assists - for(Player attacker : recentAttacks.keySet()) { - if(attacker != killer) { - attacker.getStatistics().trackAssist(config); + for(EntityAttack attack : recentAttacks) { + Entity attacker = attack.getAttacker(); + + if(attacker != killer && attacker.isPlayer()) { + ((Player)attacker).getStatistics().trackAssist(config); } } - killer.getStatistics().trackKill(config); + player.getStatistics().trackKill(config); } - EntityLoot loot = getRandomLoot(killer); + EntityLoot loot = getRandomLoot(player); if(loot != null) { Item item = loot.getItem(); if(!item.isAir()) { - killer.getInventory().addItem(item, loot.getQuantity(), true); + player.getInventory().addItem(item, loot.getQuantity(), true); } } } @@ -197,6 +195,20 @@ public float getMaxHealth() { return maxHealth; } + @Override + public float getDefense(EntityAttack attack) { + Entity attacker = attack.getAttacker(); + Player player = attacker != null && attacker.isPlayer() ? (Player)attacker : null; + + // Full defense if block is mounted and is protected + if(isMounted() && zone.isBlockProtected(mountBlock.getX(), mountBlock.getY(), player)) { + return 1.0F; + } + + // Otherwise, calculate defense + return getBaseDefense(attack.getDamageType()) + activeDefenses.getOrDefault(attack.getBaseDamage(), 0F); + } + @Override public Map getStatusConfig() { Map config = super.getStatusConfig(); @@ -239,33 +251,6 @@ public Vector2i getSize() { return size; } - public void attack(Player attacker, Item weapon) { - // Prevent damage if this entity is mounted and its mount is protected - if(!attacker.isGodMode() && isMounted() && zone.isBlockProtected(mountBlock.getX(), mountBlock.getY(), attacker)) { - return; - } - - Pair recentAttack = recentAttacks.get(attacker); - long now = System.currentTimeMillis(); - - // Reject the attack if the player already attacked this entity recently - if(!attacker.isGodMode() && recentAttack != null && now < recentAttack.getLast() + ATTACK_INVINCIBLE_TIME) { - return; - } - - float damage = attacker.isGodMode() ? 9999 : calculateDamage(weapon.getDamage(), weapon.getDamageType()); - damage(damage, attacker); - recentAttacks.put(attacker, new Pair<>(weapon, now)); - } - - public float calculateDamage(float baseDamage, DamageType type) { - return baseDamage * (1 - getDefense(type)); - } - - public Collection> getRecentAttacks() { - return Collections.unmodifiableCollection(recentAttacks.values()); - } - public void setDefense(DamageType type, float amount) { if(amount == 0) { activeDefenses.remove(type); @@ -274,14 +259,6 @@ public void setDefense(DamageType type, float amount) { } } - public float getDefense(DamageType type) { - return getDefense(type, true); - } - - public float getDefense(DamageType type, boolean includeBaseDefense) { - return (includeBaseDefense ? getBaseDefense(type) : 0) + activeDefenses.getOrDefault(type, 0F); - } - public boolean isTransient() { return !isGuard() && !isMounted(); } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ShielderBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ShielderBehavior.java index 01b2eb68..532777f8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ShielderBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ShielderBehavior.java @@ -1,19 +1,18 @@ package brainwine.gameserver.entity.npc.behavior.parts; import java.util.Arrays; -import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; +import brainwine.gameserver.entity.EntityAttack; import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.entity.npc.behavior.Behavior; import brainwine.gameserver.item.DamageType; -import brainwine.gameserver.item.Item; -import brainwine.gameserver.util.Pair; public class ShielderBehavior extends Behavior { @@ -32,11 +31,12 @@ public ShielderBehavior(@JacksonInject Npc entity) { @Override public boolean behave() { long now = System.currentTimeMillis(); - Collection> recentAttacks = entity.getRecentAttacks(); + List recentAttacks = entity.getRecentAttacks(); if(!recentAttacks.isEmpty()) { lastAttackedAt = now; - DamageType type = recentAttacks.stream().findFirst().get().getFirst().getDamageType(); + DamageType type = recentAttacks.get(recentAttacks.size() - 1).getDamageType(); + if(currentShield == null && now >= shieldStart + (recharge * 1000)) { if(defenses.contains(type)) { setShield(type); diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java index f3bab42a..76dde898 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java @@ -29,8 +29,10 @@ import brainwine.gameserver.dialog.DialogSection; import brainwine.gameserver.dialog.DialogType; import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.EntityAttack; import brainwine.gameserver.entity.EntityStatus; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.item.ItemUseType; @@ -66,7 +68,6 @@ import brainwine.gameserver.server.pipeline.Connection; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.MathUtils; -import brainwine.gameserver.util.Vector2i; import brainwine.gameserver.zone.Chunk; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; @@ -208,7 +209,7 @@ public void tick(float deltaTime) { } @Override - public void die(Player killer) { + public void die(Entity killer) { statistics.trackDeath(); sendMessageToPeers(new EntityStatusMessage(this, EntityStatus.DEAD)); // TODO killer id GameServer.getInstance().notify(String.format("%s died", name), NotificationType.CHAT); @@ -240,6 +241,21 @@ public void setHealth(float health) { sendMessage(new HealthMessage(health)); } + @Override + public void attack(Entity attacker, Item weapon, float baseDamage, DamageType damageType) { + super.attack(attacker, weapon, isGodMode() ? 0.0F : baseDamage, damageType); + } + + @Override + public float getAttackMultiplier(EntityAttack attack) { + return isGodMode() ? 9999.0F : 1.0F; + } + + @Override + public float getDefense(EntityAttack attack) { + return getNormalizedSkill(Skill.SURVIVAL) * 0.5F; + } + @Override public void setProperties(Map properties, boolean sendMessage) { super.setProperties(properties, sendMessage); @@ -1090,6 +1106,10 @@ public int getTotalSkillLevel(Skill skill) { return getSkillLevel(skill) + accessorySkillLevel; } + public float getNormalizedSkill(Skill skill) { + return getTotalSkillLevel(skill) / (float)MAX_SKILL_LEVEL; + } + public int getSkillLevel(Skill skill) { return skills.getOrDefault(skill, 1); } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java index 98a6d7a2..db5b057c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java @@ -5,6 +5,7 @@ import brainwine.gameserver.annotations.OptionalField; import brainwine.gameserver.annotations.RequestInfo; +import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; @@ -63,8 +64,8 @@ public void process(Player player) { if(id instanceof Integer) { Npc npc = player.getZone().getNpc((int)id); - if(npc != null && (player.isGodMode() || player.canSee(npc))) { - npc.attack(player, item); + if(npc != null && (player.isGodMode() || (player.canSee(npc) && !npc.wasAttackedRecently(player, Entity.ATTACK_INVINCIBLE_TIME)))) { + npc.attack(player, item, item.getDamage(), item.getDamageType()); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index f8cd9940..d81ef61e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -297,7 +297,7 @@ public void explode(int x, int y, float radius, Entity cause, String effect) { explode(x, y, radius, cause, false, 0, null, effect); } - public void explode(int x, int y, float radius, Entity cause, boolean destructive, float damage, DamageType damageType, String effect) { + public void explode(int x, int y, float radius, Entity cause, boolean destructive, float baseDamage, DamageType damageType, String effect) { // Do nothing if the chunk at the target location isn't loaded if(!isChunkLoaded(x, y)) { return; @@ -399,10 +399,9 @@ public void explode(int x, int y, float radius, Entity cause, boolean destructiv // Cast a ray from the explosion to the entity and damage it if it reaches it if(entity.canSee(x, y)) { double distance = MathUtils.distance(x, y, entity.getX(), entity.getY()); - entity.damage((float)(damage - distance), player); + float damage = (float)(baseDamage - distance); + entity.attack(player, null, damage, damageType); // TODO maybe track the bomb used to damage the entity } - - // TODO generic entity damaging is not very intuitive and needs to be worked on. } } From dcb5e8a107e08e6737cdea5483aae9ebda328ceb Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 18 Feb 2024 02:31:53 +0100 Subject: [PATCH 011/176] Fix lava bombs --- .../server/requests/BlockPlaceRequest.java | 33 +++++++------------ .../java/brainwine/gameserver/zone/Zone.java | 30 +++++++++++++++++ 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java index c6cd0155..8c6688e9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java @@ -168,32 +168,21 @@ private void createBlockTimer(Zone zone, Player player) { }; break; case "bomb-water": + task = () -> { + zone.explode(x, y, value, player, false, value, DamageType.COLD, "bomb-large"); + zone.explodeLiquid(x, y, 4, "liquid/water"); + }; + break; case "bomb-acid": + task = () -> { + zone.explode(x, y, value, player, false, value, DamageType.ACID, "bomb-large"); + zone.explodeLiquid(x, y, 4, "liquid/acid"); + }; + break; case "bomb-lava": task = () -> { zone.explode(x, y, value, player, false, value, DamageType.FIRE, "bomb-large"); - Item liquid = Item.get(String.format("liquid/%s", type.replace("bomb-", ""))); - int range = 4; - - // Do nothing if there is no liquid to place - if(liquid.isAir()) { - return; - } - - // Place liquid blocks around the explosion - for(int i = x - range; i <= x + range; i++) { - for(int j = y - range; j <= y + range; j++) { - // Skip if not in range - if(!MathUtils.inRange(x, y, i, j, range)) { - continue; - } - - // Place liquid if target block isn't solid - if(!zone.isBlockSolid(i, j, true)) { - zone.updateBlock(i, j, Layer.LIQUID, liquid, 5); - } - } - } + zone.explodeLiquid(x, y, 4, "liquid/magma"); }; break; default: diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index d81ef61e..ad4042bf 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -405,6 +405,36 @@ public void explode(int x, int y, float radius, Entity cause, boolean destructiv } } + public void explodeLiquid(int x, int y, int range, int liquid) { + explodeLiquid(x, y, range, ItemRegistry.getItem(liquid)); + } + + public void explodeLiquid(int x, int y, int range, String liquid) { + explodeLiquid(x, y, range, ItemRegistry.getItem(liquid)); + } + + public void explodeLiquid(int x, int y, int range, Item liquid) { + // Do nothing if liquid isn't actually a liquid + if(liquid.getLayer() != Layer.LIQUID) { + return; + } + + // Place liquid blocks around the explosion + for(int i = x - range; i <= x + range; i++) { + for(int j = y - range; j <= y + range; j++) { + // Skip if not in range + if(!MathUtils.inRange(x, y, i, j, range)) { + continue; + } + + // Place liquid if target block isn't solid + if(!isBlockSolid(i, j, true)) { + updateBlock(i, j, Layer.LIQUID, liquid, 5); + } + } + } + } + public boolean isBlockNatural(int x, int y) { return areCoordinatesInBounds(x, y) && getBlock(x, y).isNatural(); } From f986587ef1136e078e60ecbcea9f0999f2048c54 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 18 Feb 2024 18:08:38 +0100 Subject: [PATCH 012/176] Fix inventory use sync issue with v2 clients --- .../gameserver/server/requests/InventoryUseRequest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java index db5b057c..a7a6b4ee 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java @@ -30,7 +30,7 @@ public class InventoryUseRequest extends PlayerRequest { @Override public void process(Player player) { // Don't do anything if the player is dead or doesn't own this item - if(player.isDead() || !player.getInventory().hasItem(item)) { + if(player.isDead() || (!item.isAir() && !player.getInventory().hasItem(item))) { return; } @@ -46,7 +46,7 @@ public void process(Player player) { } // Send item use data to other players in the zone - player.sendMessageToPeers(new EntityItemUseMessage(player.getId(), type, item, status)); + player.sendMessageToTrackers(new EntityItemUseMessage(player.getId(), type, item, status)); // Lovely type ambiguity. Always nice. if(item.isWeapon() && status == 1) { From 29dd9ac540f7a5815664a5fd9cebe8558d1141fd Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 18 Feb 2024 18:25:54 +0100 Subject: [PATCH 013/176] Add note functionality --- .../gameserver/dialog/DialogSection.java | 21 +++- .../entity/player/NotificationType.java | 1 + .../gameserver/item/ItemUseType.java | 2 + .../interactions/ContainerInteraction.java | 3 +- .../item/interactions/NoteInteraction.java | 98 +++++++++++++++++++ .../item/interactions/SwitchInteraction.java | 3 +- .../server/requests/BlockUseRequest.java | 15 +-- .../gameserver/zone/EntityManager.java | 2 +- .../brainwine/gameserver/zone/MetaBlock.java | 13 ++- .../java/brainwine/gameserver/zone/Zone.java | 32 +++--- 10 files changed, 163 insertions(+), 27 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/NoteInteraction.java diff --git a/gameserver/src/main/java/brainwine/gameserver/dialog/DialogSection.java b/gameserver/src/main/java/brainwine/gameserver/dialog/DialogSection.java index 9374d8a5..5f6f48f2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/dialog/DialogSection.java +++ b/gameserver/src/main/java/brainwine/gameserver/dialog/DialogSection.java @@ -3,13 +3,15 @@ import java.util.ArrayList; import java.util.List; +import com.fasterxml.jackson.annotation.JsonFormat; 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 com.fasterxml.jackson.annotation.JsonFormat.Shape; import brainwine.gameserver.dialog.input.DialogInput; - -import com.fasterxml.jackson.annotation.JsonProperty; +import brainwine.gameserver.util.Vector2i; @JsonInclude(Include.NON_DEFAULT) @JsonIgnoreProperties(ignoreUnknown = true) @@ -20,6 +22,7 @@ public class DialogSection { private String text; private String textColor; private double textScale; + private Vector2i location; private DialogInput input; public DialogSection addItem(DialogListItem item) { @@ -75,6 +78,20 @@ public double getTextScale() { return textScale; } + /** + * v2 clients only! + */ + public DialogSection setLocation(int x, int y) { + this.location = new Vector2i(x, y); + return this; + } + + @JsonProperty("map") + @JsonFormat(shape = Shape.ARRAY) + public Vector2i getLocation() { + return location; + } + public DialogSection setInput(DialogInput input) { this.input = input; return this; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/NotificationType.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/NotificationType.java index aaeebc18..a6126f1d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/NotificationType.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/NotificationType.java @@ -13,6 +13,7 @@ public enum NotificationType { ACCOMPLISHMENT(10), PEER_ACCOMPLISHMENT(11), REWARD(12), // v2 only + NOTE(13), // v2 only CHAT(20), LEVEL_UP(21), // v3 only ACHIEVEMENT(22), // v3 only diff --git a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java index 7ba29082..0042bb58 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java @@ -8,6 +8,7 @@ import brainwine.gameserver.item.interactions.ContainerInteraction; import brainwine.gameserver.item.interactions.DialogInteraction; import brainwine.gameserver.item.interactions.ItemInteraction; +import brainwine.gameserver.item.interactions.NoteInteraction; import brainwine.gameserver.item.interactions.SpawnInteraction; import brainwine.gameserver.item.interactions.SpawnTeleportInteraction; import brainwine.gameserver.item.interactions.SwitchInteraction; @@ -31,6 +32,7 @@ public enum ItemUseType { FIELDABLE, FLY, MULTI, + NOTE(new NoteInteraction()), PLENTY, PROTECTED, PUBLIC, diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java index 192db113..14971f12 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java @@ -81,8 +81,7 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i // Update container mod if(!plenty && !metaBlock.hasProperty("$")) { - Player owner = GameServer.getInstance().getPlayerManager().getPlayerById(metaBlock.getOwner()); - zone.updateBlock(x, y, Layer.FRONT, item, 0, owner, metaBlock.getMetadata()); + zone.updateBlock(x, y, Layer.FRONT, item, 0, metaBlock.getOwner(), metaBlock.getMetadata()); } } } diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/NoteInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/NoteInteraction.java new file mode 100644 index 00000000..a58d5874 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/NoteInteraction.java @@ -0,0 +1,98 @@ +package brainwine.gameserver.item.interactions; + +import java.util.Collections; +import java.util.List; + +import brainwine.gameserver.dialog.Dialog; +import brainwine.gameserver.dialog.DialogHelper; +import brainwine.gameserver.dialog.DialogSection; +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.player.NotificationType; +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.Layer; +import brainwine.gameserver.util.MapHelper; +import brainwine.gameserver.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; + +public class NoteInteraction implements ItemInteraction { + + @Override + public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, + Object config, Object[] data) { + // Do nothing if entity is not a player + if(!entity.isPlayer()) { + return; + } + + // Do nothing if the right data isn't present + if(metaBlock == null || data != null) { + return; + } + + Player player = (Player)entity; + + // Check if note contains a location + if(metaBlock.hasProperty("l")) { + List location = MapHelper.getList(metaBlock.getMetadata(), "l", Collections.emptyList()); + String text = metaBlock.getStringProperty("t"); + + // Do nothing if location data is invalid + if(location.size() != 2) { + return; + } + + int locationX = location.get(0); + int locationY = location.get(1); + + // Create dialog based on player version since v3 doesn't support map dialogs + if(player.isV3()) { + Dialog dialog = new Dialog() + .addSection(new DialogSection() + .setTitle("The note reads:") + .setText(text)) + .addSection(new DialogSection() + .setText(zone.getReadableCoordinates(locationX, locationY))); + player.showDialog(dialog); + } else { + // v2 dialog + Dialog dialog = new Dialog() + .addSection(new DialogSection() + .setTitle(text)) + .addSection(new DialogSection() + .setLocation(locationX, locationY)); + player.notify(dialog, NotificationType.NOTE); + } + + return; + } + + // Do nothing if player owns this note + if(metaBlock.isOwnedBy(player)) { + return; + } + + // Build string from note segments + String[] keys = { "t1", "t2", "t3", "t4", "t5", "t6" }; + StringBuilder builder = new StringBuilder(); + + for(int i = 0; i < keys.length; i++) { + String text = metaBlock.getStringProperty(keys[i]); + + // Skip if text is null or empty + if(text == null || text.isEmpty()) { + continue; + } + + // Append space if necessary + if(i > 0) { + builder.append(" "); + } + + builder.append(text); + } + + // Show note text in dialog + player.showDialog(DialogHelper.messageDialog("The note reads:", builder.toString())); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java index 8adf44e5..9c2117b3 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java @@ -7,7 +7,6 @@ import org.apache.commons.text.WordUtils; -import brainwine.gameserver.GameServer; import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.EntityConfig; import brainwine.gameserver.entity.EntityRegistry; @@ -91,7 +90,7 @@ private void switchBlock(Zone zone, Entity entity, int x, int y, int mod, MetaBl return; } - Player owner = metaBlock == null ? null : GameServer.getInstance().getPlayerManager().getPlayerById(metaBlock.getOwner()); + Player owner = metaBlock == null ? null : metaBlock.getOwner(); Map metadata = metaBlock == null ? null : metaBlock.getMetadata(); Item item = metaBlock.getItem(); Object config = item.getUse(ItemUseType.SWITCHED); diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockUseRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockUseRequest.java index dd669bbb..c8ecdc38 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockUseRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockUseRequest.java @@ -2,7 +2,6 @@ import java.util.Map; -import brainwine.gameserver.GameServer; import brainwine.gameserver.annotations.OptionalField; import brainwine.gameserver.annotations.RequestInfo; import brainwine.gameserver.entity.player.Player; @@ -51,7 +50,7 @@ public void process(Player player) { // Check if block is owned by another player if(metaBlock != null && item.hasUse(ItemUseType.PROTECTED)) { - Player owner = GameServer.getInstance().getPlayerManager().getPlayerById(metaBlock.getOwner()); + Player owner = metaBlock.getOwner(); if(player != owner) { if(item.hasUse(ItemUseType.PUBLIC)) { @@ -59,10 +58,14 @@ public void process(Player player) { // TODO implement other cases switch(publicUse) { - case "owner": - player.notify(String.format("This %s is owned by %s.", - item.getTitle().toLowerCase(), owner == null ? "somebody else" : owner.getName())); - break; + case "owner": + player.notify(String.format("This %s is owned by %s.", + item.getTitle().toLowerCase(), owner == null ? "somebody else" : owner.getName())); + break; + case "note": + ItemUseType.NOTE.getInteraction().interact(zone, player, x, y, layer, item, mod, metaBlock, null, data); + break; + default: break; } } else { player.notify("Sorry, that belongs to somebody else."); diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java index 0b71f8d1..35ec8605 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java @@ -239,7 +239,7 @@ public void trySpawnBlockEntity(int x, int y) { // Set owner entity if it has one if(metaBlock != null && metaBlock.hasOwner()) { - entity.setOwner(GameServer.getInstance().getPlayerManager().getPlayerById(metaBlock.getOwner())); + entity.setOwner(metaBlock.getOwner()); } entity.setMountBlock(x, y); diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java b/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java index b2e40ae6..b4f53e7a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; +import brainwine.gameserver.GameServer; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; @@ -71,7 +72,17 @@ public boolean hasOwner() { return owner != null; } - public String getOwner() { + public boolean isOwnedBy(Player player) { + return player != null && player.getDocumentId().equals(owner); + } + + @JsonIgnore + public Player getOwner() { + return GameServer.getInstance().getPlayerManager().getPlayerById(owner); + } + + @JsonProperty("owner") + private String getOwnerId() { return owner; } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index ad4042bf..03f1bb1c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -496,18 +496,18 @@ public boolean isBlockProtected(int x, int y) { return isBlockProtected(x, y, null); } - public boolean isBlockProtected(int x, int y, Player from) { - return isBlockProtected(x, y, from, fieldBlocks.values()); + public boolean isBlockProtected(int x, int y, Player player) { + return isBlockProtected(x, y, player, fieldBlocks.values()); } - public boolean isBlockProtected(int x, int y, Player from, Collection fieldBlocks) { + public boolean isBlockProtected(int x, int y, Player player, Collection fieldBlocks) { for(MetaBlock fieldBlock : fieldBlocks) { Item item = fieldBlock.getItem(); int fX = fieldBlock.getX(); int fY = fieldBlock.getY(); int field = fieldBlock.getItem().getField(); - if(from == null || !ownsMetaBlock(fieldBlock, from)) { + if(player == null || !fieldBlock.isOwnedBy(player)) { if(item.isDish()) { if(MathUtils.inRange(x, y, fX, fY, field)) { return true; @@ -529,7 +529,7 @@ public boolean willDishOverlap(int x, int y, int field, Player player) { int fY = fieldBlock.getY(); int fField = fieldBlock.getItem().getField(); - if(MathUtils.inRange(x, y, fX, fY, field + fField) && !ownsMetaBlock(fieldBlock, player)) { + if(MathUtils.inRange(x, y, fX, fY, field + fField) && !fieldBlock.isOwnedBy(player)) { return true; } } @@ -1035,14 +1035,6 @@ protected void setMetaBlocks(List metaBlocks) { indexDungeons(); } - private boolean ownsMetaBlock(MetaBlock metaBlock, Player player) { - if(!metaBlock.hasOwner()) { - return false; - } - - return player.getDocumentId().equals(metaBlock.getOwner()); - } - public MetaBlock getMetaBlock(int x, int y) { return metaBlocks.get(getBlockIndex(x, y)); } @@ -1162,6 +1154,20 @@ public int settleLiquids() { return liquidManager.settleLiquids(); } + /** + * @return The specified coordinates in a player-readable format + * For example, {@code x: 200 y: 300} in a plain biome becomes {@code 800 west, 100 below} + */ + public String getReadableCoordinates(int x, int y) { + int center = width / 2; + int surface = biome == Biome.DEEP ? -1000 : 200; + String directionX = x < center ? "west" : x > center ? "east" : "central"; + String directionY = y > surface ? "below" : "above"; + String coordX = String.format("%s %s", Math.abs(x - center), directionX); + String coordY = String.format("%s %s", Math.abs(y - surface), directionY); + return String.format("%s, %s", coordX, coordY); + } + public boolean areCoordinatesInBounds(int x, int y) { return x >= 0 && y >= 0 && x < width && y < height; } From df698a349ba40b8b9b57bf62bf837411443c997a Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 18 Feb 2024 18:39:51 +0100 Subject: [PATCH 014/176] Check if entity is dead before applying damage --- .../java/brainwine/gameserver/entity/Entity.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java index 879871ea..e33c1159 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java @@ -86,9 +86,15 @@ public void damage(float amount) { } public void damage(float amount, Entity attacker) { + // Do nothing if entity is already dead + if(isDead()) { + return; + } + setHealth(health - amount); - if(health <= 0) { + // Handle entity death if it died as a result of taking this damage + if(isDead()) { die(attacker); } @@ -96,6 +102,11 @@ public void damage(float amount, Entity attacker) { } public void attack(Entity attacker, Item weapon, float baseDamage, DamageType damageType) { + // Ignore attack if entity is already dead + if(isDead()) { + return; + } + EntityAttack attack = new EntityAttack(attacker, weapon, baseDamage, damageType); boolean ignoreDefense = attacker != null && attacker.isPlayer() && ((Player)attacker).isGodMode(); float attackMultiplier = attacker != null ? Math.max(0.0F, attacker.getAttackMultiplier(attack)) : 1.0F; From 45787c3dba72330768e7da693201761b17b660b0 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 18 Feb 2024 18:52:19 +0100 Subject: [PATCH 015/176] Damage check --- .../src/main/java/brainwine/gameserver/entity/Entity.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java index e33c1159..3a6e1387 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java @@ -107,6 +107,11 @@ public void attack(Entity attacker, Item weapon, float baseDamage, DamageType da return; } + // Ignore attack if there is no damage to deal + if(baseDamage <= 0 || damageType == null || damageType == DamageType.NONE) { + return; + } + EntityAttack attack = new EntityAttack(attacker, weapon, baseDamage, damageType); boolean ignoreDefense = attacker != null && attacker.isPlayer() && ((Player)attacker).isGodMode(); float attackMultiplier = attacker != null ? Math.max(0.0F, attacker.getAttackMultiplier(attack)) : 1.0F; From 9ec3175eb1cc8e56de7f90a75c034a49b79b8037 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 18 Feb 2024 20:30:40 +0100 Subject: [PATCH 016/176] Improve damage tracking --- .../brainwine/gameserver/entity/Entity.java | 64 ++++++++++++------- .../brainwine/gameserver/entity/npc/Npc.java | 59 +++++++++-------- .../npc/behavior/parts/ShielderBehavior.java | 6 +- .../gameserver/entity/player/Player.java | 13 ++-- .../item/interactions/SwitchInteraction.java | 2 +- .../server/requests/HealthRequest.java | 15 ++++- .../java/brainwine/gameserver/zone/Zone.java | 5 +- 7 files changed, 99 insertions(+), 65 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java index 3a6e1387..9700335a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java @@ -47,6 +47,8 @@ public abstract class Entity { protected int targetY; protected FacingDirection direction = FacingDirection.WEST; protected int animation; + protected boolean invulnerable; + protected EntityAttack lastAttack; // Used for tracking in entity deaths -- do not use this for anything else! protected long lastDamagedAt; public Entity(Zone zone) { @@ -71,7 +73,7 @@ public void tick(float deltaTime) { recentAttacks.removeIf(attack -> now >= attack.getTime() + ATTACK_RETENTION_TIME); } - public void die(Entity killer) { + public void die(EntityAttack cause) { // Override } @@ -81,44 +83,42 @@ public void heal(float amount) { } } - public void damage(float amount) { - damage(amount, null); + public void attack(Entity attacker, Item weapon, float baseDamage, DamageType damageType) { + attack(attacker, weapon, baseDamage, damageType, false); } - public void damage(float amount, Entity attacker) { - // Do nothing if entity is already dead - if(isDead()) { + public void attack(Entity attacker, Item weapon, float baseDamage, DamageType damageType, boolean trueDamage) { + // Ignore attack if entity is dead or invulnerable + if(isDead() || isInvulnerable()) { return; } - setHealth(health - amount); - - // Handle entity death if it died as a result of taking this damage - if(isDead()) { - die(attacker); + // Ignore attack if there is no damage to deal + if(baseDamage <= 0 || damageType == null || damageType == DamageType.NONE) { + return; } + EntityAttack attack = new EntityAttack(attacker, weapon, baseDamage, damageType); + recentAttacks.add(attack); + lastAttack = attack; lastDamagedAt = System.currentTimeMillis(); - } - - public void attack(Entity attacker, Item weapon, float baseDamage, DamageType damageType) { - // Ignore attack if entity is already dead - if(isDead()) { + + // Kill entity if attacker is a player in god mode + if(attacker != null && attacker.isPlayer() && ((Player)attacker).isGodMode()) { + setHealth(0.0F); return; } - // Ignore attack if there is no damage to deal - if(baseDamage <= 0 || damageType == null || damageType == DamageType.NONE) { + // Ignore multipliers if true damage should be dealt + if(trueDamage) { + setHealth(health - baseDamage); return; } - EntityAttack attack = new EntityAttack(attacker, weapon, baseDamage, damageType); - boolean ignoreDefense = attacker != null && attacker.isPlayer() && ((Player)attacker).isGodMode(); float attackMultiplier = attacker != null ? Math.max(0.0F, attacker.getAttackMultiplier(attack)) : 1.0F; float defense = Math.max(0.0F, 1.0F - getDefense(attack)); - float damage = baseDamage * attackMultiplier * (ignoreDefense ? 1.0F : defense); - damage(damage, attacker); - recentAttacks.add(attack); + float damage = baseDamage * attackMultiplier * defense; + setHealth(health - damage); } public float getAttackMultiplier(EntityAttack attack) { @@ -209,6 +209,10 @@ public boolean wasAttackedRecently(Entity entity, int delay) { return recentAttacks.stream().filter(attack -> attack.getAttacker() == entity && System.currentTimeMillis() < attack.getTime() + delay).findFirst().isPresent(); } + public EntityAttack getMostRecentAttack() { + return recentAttacks.isEmpty() ? null : recentAttacks.get(recentAttacks.size() - 1); + } + public List getRecentAttacks() { return Collections.unmodifiableList(recentAttacks); } @@ -244,6 +248,12 @@ public boolean isDead() { public void setHealth(float health) { float maxHealth = getMaxHealth(); this.health = health < 0 ? 0 : health > maxHealth ? maxHealth : health; + + if(this.health <= 0.0F) { + die(lastAttack); + } + + lastAttack = null; } public float getHealth() { @@ -313,6 +323,14 @@ public int getAnimation() { return animation; } + public void setInvulnerable(boolean invulnerable) { + this.invulnerable = invulnerable; + } + + public boolean isInvulnerable() { + return invulnerable; + } + public void setZone(Zone zone) { this.zone = zone; } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java index 5dd30440..eacd0f14 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java @@ -142,17 +142,42 @@ public void tick(float deltaTime) { } @Override - public void die(Entity killer) { + public void die(EntityAttack cause) { + // Remove itself from the guard block metadata if it was guarding one + if(isGuard()) { + MetaBlock metaBlock = zone.getMetaBlock(guardBlock.getX(), guardBlock.getY()); + + if(metaBlock != null) { + List guards = MapHelper.getList(metaBlock.getMetadata(), "!"); + + if(guards != null) { + guards.remove(typeName); + } + } + } + + // Destroy mount block if it has one + if(isMounted()) { + zone.updateBlock(mountBlock.getX(), mountBlock.getY(), Layer.FRONT, 0); + } + + // Do nothing else if cause data isn't present + if(cause == null) { + return; + } + + Entity killer = cause.getAttacker(); + // Grant loot & track kill if(!artificial && killer != null && killer.isPlayer()) { Player player = (Player)killer; if(!isPlayerPlaced()) { // Track assists - for(EntityAttack attack : recentAttacks) { - Entity attacker = attack.getAttacker(); + for(EntityAttack recentAttack : recentAttacks) { + Entity attacker = recentAttack.getAttacker(); - if(attacker != killer && attacker.isPlayer()) { + if(attacker != player && attacker.isPlayer()) { ((Player)attacker).getStatistics().trackAssist(config); } } @@ -160,7 +185,7 @@ public void die(Entity killer) { player.getStatistics().trackKill(config); } - EntityLoot loot = getRandomLoot(player); + EntityLoot loot = getRandomLoot(player, cause.getWeapon()); if(loot != null) { Item item = loot.getItem(); @@ -170,24 +195,6 @@ public void die(Entity killer) { } } } - - // Remove itself from the guard block metadata if it was guarding one - if(isGuard()) { - MetaBlock metaBlock = zone.getMetaBlock(guardBlock.getX(), guardBlock.getY()); - - if(metaBlock != null) { - List guards = MapHelper.getList(metaBlock.getMetadata(), "!"); - - if(guards != null) { - guards.remove(typeName); - } - } - } - - // Destroy mount block if it has one - if(isMounted()) { - zone.updateBlock(mountBlock.getX(), mountBlock.getY(), Layer.FRONT, 0); - } } @Override @@ -206,7 +213,7 @@ public float getDefense(EntityAttack attack) { } // Otherwise, calculate defense - return getBaseDefense(attack.getDamageType()) + activeDefenses.getOrDefault(attack.getBaseDamage(), 0F); + return getBaseDefense(attack.getDamageType()) + activeDefenses.getOrDefault(attack.getDamageType(), 0F); } @Override @@ -263,9 +270,7 @@ public boolean isTransient() { return !isGuard() && !isMounted(); } - public EntityLoot getRandomLoot(Player awardee) { - Item weapon = awardee.getHeldItem(); - + public EntityLoot getRandomLoot(Player awardee, Item weapon) { if(isOwnedBy(awardee)) { return placedLoot.next(); } else if(lootByWeapon.containsKey(weapon)) { diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ShielderBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ShielderBehavior.java index 532777f8..53757436 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ShielderBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ShielderBehavior.java @@ -31,11 +31,11 @@ public ShielderBehavior(@JacksonInject Npc entity) { @Override public boolean behave() { long now = System.currentTimeMillis(); - List recentAttacks = entity.getRecentAttacks(); + EntityAttack attack = entity.getMostRecentAttack(); - if(!recentAttacks.isEmpty()) { + if(attack != null) { lastAttackedAt = now; - DamageType type = recentAttacks.get(recentAttacks.size() - 1).getDamageType(); + DamageType type = attack.getDamageType(); if(currentShield == null && now >= shieldStart + (recharge * 1000)) { if(defenses.contains(type)) { diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java index 76dde898..60f78bc5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java @@ -32,7 +32,6 @@ import brainwine.gameserver.entity.EntityAttack; import brainwine.gameserver.entity.EntityStatus; import brainwine.gameserver.entity.npc.Npc; -import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.item.ItemUseType; @@ -209,7 +208,7 @@ public void tick(float deltaTime) { } @Override - public void die(Entity killer) { + public void die(EntityAttack cause) { statistics.trackDeath(); sendMessageToPeers(new EntityStatusMessage(this, EntityStatus.DEAD)); // TODO killer id GameServer.getInstance().notify(String.format("%s died", name), NotificationType.CHAT); @@ -241,11 +240,6 @@ public void setHealth(float health) { sendMessage(new HealthMessage(health)); } - @Override - public void attack(Entity attacker, Item weapon, float baseDamage, DamageType damageType) { - super.attack(attacker, weapon, isGodMode() ? 0.0F : baseDamage, damageType); - } - @Override public float getAttackMultiplier(EntityAttack attack) { return isGodMode() ? 9999.0F : 1.0F; @@ -256,6 +250,11 @@ public float getDefense(EntityAttack attack) { return getNormalizedSkill(Skill.SURVIVAL) * 0.5F; } + @Override + public boolean isInvulnerable() { + return invulnerable || isGodMode(); + } + @Override public void setProperties(Map properties, boolean sendMessage) { super.setProperties(properties, sendMessage); diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java index 9c2117b3..3bf963b1 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java @@ -156,7 +156,7 @@ private void switchExploder(Zone zone, Entity entity, MetaBlock metaBlock) { } // Create explosion - DamageType damageType = DamageType.fromName(type); + DamageType damageType = type.equalsIgnoreCase("electric") ? DamageType.ENERGY : DamageType.fromName(type); String effect = String.format("bomb-%s", type.toLowerCase()); zone.explode(metaBlock.getX(), metaBlock.getY(), 6, entity, false, 6, damageType, effect); } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/HealthRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/HealthRequest.java index b1e0e94a..4929b316 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/HealthRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/HealthRequest.java @@ -2,7 +2,10 @@ import brainwine.gameserver.annotations.OptionalField; import brainwine.gameserver.annotations.RequestInfo; +import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.DamageType; +import brainwine.gameserver.item.Item; import brainwine.gameserver.server.PlayerRequest; @RequestInfo(id = 18) @@ -18,10 +21,18 @@ public class HealthRequest extends PlayerRequest { public void process(Player player) { float health = this.health / 1000.0F; - if(!player.isGodMode() && health >= player.getHealth()) { + // Prevent self-healing unless player has god mode enabled + if(health >= player.getHealth()) { + if(player.isGodMode()) { + player.setHealth(health); + } + return; } - player.damage(player.getHealth() - health, null); + // TODO attacker ID is always zero on v3 and damage type seems to do nothing on both v2 and v3 so we'll just have to do what we can here + Entity attacker = player.getZone().getEntity(attackerId); + float damage = player.getHealth() - health; + player.attack(attacker, Item.AIR, damage, DamageType.ACID, true); // Deal true damage; the client should have already applied any damage modifiers } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 03f1bb1c..6ced37e4 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -305,9 +305,10 @@ public void explode(int x, int y, float radius, Entity cause, boolean destructiv sendMessageToChunk(new EffectMessage(x + 0.5f, y + 0.5f, effect, radius), getChunk(x, y)); Player player = cause instanceof Player ? (Player)cause : null; + Item item = getBlock(x, y).getFrontItem(); // Try to destroy the block at the source of the explosion - if(getBlock(x, y).getFrontItem().getFieldability() == Fieldability.FALSE) { + if(item.getFieldability() == Fieldability.FALSE) { updateBlock(x, y, Layer.FRONT, 0); if(destructive && !isBlockProtected(x, y, player)) { @@ -400,7 +401,7 @@ public void explode(int x, int y, float radius, Entity cause, boolean destructiv if(entity.canSee(x, y)) { double distance = MathUtils.distance(x, y, entity.getX(), entity.getY()); float damage = (float)(baseDamage - distance); - entity.attack(player, null, damage, damageType); // TODO maybe track the bomb used to damage the entity + entity.attack(cause, item, damage, damageType); } } } From 9dc5cbd34fb64bda2d51fd54e3fc50f93a1d65a1 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 18 Feb 2024 21:42:50 +0100 Subject: [PATCH 017/176] Lazy fix for potential crashes --- .../main/java/brainwine/gameserver/util/MapHelper.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/util/MapHelper.java b/gameserver/src/main/java/brainwine/gameserver/util/MapHelper.java index 5f0b88e5..4890cf20 100644 --- a/gameserver/src/main/java/brainwine/gameserver/util/MapHelper.java +++ b/gameserver/src/main/java/brainwine/gameserver/util/MapHelper.java @@ -50,6 +50,10 @@ public static Map map(Class keyType, Class valueType, Object. } public static void put(Map map, String path, Object value) { + if(path == null) { + return; + } + String[] segments = path.split("\\."); Map current = (Map)map; @@ -76,6 +80,10 @@ public static T get(Map map, String path, Class type) { } public static T get(Map map, String path, Class type, T def) { + if(path == null) { + return def; + } + String[] segments = path.split("\\."); Map current = (Map)map; From 188b78d5b1e2099ae69b5f646fe1fcb2d49400c7 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 18 Feb 2024 22:04:58 +0100 Subject: [PATCH 018/176] Implement persistence for certain NPCs --- .../java/brainwine/gameserver/Naming.java | 117 ++++++++++++++++++ .../brainwine/gameserver/entity/Entity.java | 23 ++-- .../gameserver/entity/EntityConfig.java | 10 ++ .../brainwine/gameserver/entity/npc/Npc.java | 14 ++- .../gameserver/entity/npc/NpcData.java | 47 +++++++ .../gameserver/zone/EntityManager.java | 36 ++++-- .../java/brainwine/gameserver/zone/Zone.java | 9 ++ .../gameserver/zone/ZoneManager.java | 33 ++++- .../gameserver/zone/gen/ZoneGenerator.java | 39 +----- 9 files changed, 266 insertions(+), 62 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/Naming.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/npc/NpcData.java diff --git a/gameserver/src/main/java/brainwine/gameserver/Naming.java b/gameserver/src/main/java/brainwine/gameserver/Naming.java new file mode 100644 index 00000000..48cc0a28 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/Naming.java @@ -0,0 +1,117 @@ +package brainwine.gameserver; + +/** + * TODO all I'm doing here is moving the problem somewhere else. + * + * Entity names are sourced from: https://github.com/bytebin/deepworld-gameserver/blob/master/config/fake.yml + */ +public class Naming { + + private static final String[] ZONE_FIRST_NAMES = { + "Malvern", "Tralee", "Horncastle", "Old", "Westwood", + "Citta", "Tadley", "Mossley", "West", "East", + "North", "South", "Wadpen", "Githam", "Soatnust", + "Highworth", "Creakynip", "Upper", "Lower", "Cannock", + "Dovercourt", "Limerick", "Pickering", "Glumshed", "Crusthack", + "Osyltyr", "Aberstaple", "New", "Stroud", "Crumclum", + "Crumsidle", "Bankswund", "Fiddletrast", "Bournpan", "St.", + "Funderbost", "Bexwoddly", "Pilkingheld", "Wittlepen", "Rabbitbleaker", + "Griffingumby", "Guilthead", "Bigglelund", "Bunnymold", "Rosesidle", + "Crushthorn", "Tanlyward", "Ahncrace", "Pilkingking", "Dingstrath", + "Axebury", "Ginglingtap", "Ballybibby", "Shadehoven" + }; + + private static final String[] ZONE_LAST_NAMES = { + "Falls", "Alloa", "Glen", "Way", "Dolente", + "Peak", "Heights", "Creek", "Banffshire", "Chagford", + "Gorge", "Valley", "Catacombs", "Depths", "Mines", + "Crickbridge", "Guildbost", "Pits", "Vaults", "Ruins", + "Dell", "Keep", "Chatterdin", "Scrimmance", "Gitwick", + "Ridge", "Alresford", "Place", "Bridge", "Glade", + "Mill", "Court", "Dooftory", "Hills", "Specklewint", + "Grove", "Aylesbury", "Wagwouth", "Russetcumby", "Point", + "Canyon", "Cranwarry", "Bluff", "Passage", "Crantippy", + "Kerbodome", "Dale", "Cemetery" + }; + + public static final String[] ENTITY_FIRST_NAMES = { + "Aaron", "Abby", "Abigale", "Abraham", "Ada", "Adella", "Agnes", "Alan", + "Albert", "Alexander", "Allie", "Almira", "Almyra", "Alonzo", "Alva", "Ambrose", + "Amelia", "Amon", "Amos", "Andrew", "Ann", "Annie", "Aquilla", "Archibald", + "Arnold", "Arrah", "Asa", "Augustus", "Barnabas", "Bartholomew", "Beatrice", "Becky", + "Benedict", "Benjamin", "Bennet", "Bernard", "Bernice", "Bertram", "Bess", "Bessie", + "Beth", "Betsy", "Buford", "Byron", "Calvin", "Charity", "Charles", "Charlotte", + "Chastity", "Christopher", "Claire", "Clarence", "Clement", "Clinton", "Cole", "Columbus", + "Commodore Perry", "Constance", "Cynthia", "Daniel", "David", "Dick", "Dorothy", "Edith", + "Edmund", "Edna", "Edward", "Edwin", "Edwina", "Eldon", "Eleanor", "Eli", + "Elijah", "Eliza", "Elizabeth", "Ella", "Ellie", "Elvira", "Emma", "Emmett", + "Enoch", "Esther", "Ethel", "Ettie", "Eudora", "Eva", "Ezekiel", "Ezra", + "Fanny", "Fidelia", "Flora", "Florence", "Frances", "Francis", "Franklin", "Frederick", + "Gabriel", "Garrett", "Geneve", "Genevieve", "George", "George", "Georgia", "Gertie", + "Gertrude", "Gideon", "Gilbert", "Ginny", "Gladys", "Grace", "Granville", "Hannah", + "Harland", "Harold", "Harrison", "Harvey", "Hattie", "Helen", "Helene", "Henrietta", + "Henry", "Hester", "Hettie", "Hiram", "Hope", "Horace", "Horatio", "Hortence", + "Hugh", "Isaac", "Isaac Newton", "Isabella", "Isaiah", "Israel", "Jacob", "James", + "Jane", "Jasper", "Jedediah", "Jefferson", "Jennie", "Jeptha", "Jessamine", "Jesse", + "Joel", "John Paul", "John Wesley", "Jonathan", "Joseph", "Josephine", "Josephus", "Joshua", + "Josiah", "Judith", "Julia", "Julian", "Juliet", "Julius", "Katherine", "Lafayette", + "Laura", "Lawrence", "Leah", "Leander", "Lenora", "Les", "Letitia", "Levi", + "Levi", "Lewis", "Lila", "Lilly", "Liza", "Lorena", "Lorraine", "Lottie", + "Louis", "Louisa", "Louise", "Lucas", "Lucas", "Lucian", "Lucian", "Lucius", + "Lucius", "Lucy", "Luke", "Luke", "Lulu", "Luther", "Luther", "Lydia", + "Mahulda", "Marcellus", "Margaret", "Mark", "Martha", "Martin", "Mary", "Mary Elizabeth", + "Mary Frances", "Masheck", "Matilda", "Matthew", "Maude", "Maurice", "Maxine", "Maxwell", + "Mercy", "Meriwether", "Meriwether Lewis", "Merrill", "Mildred", "Minerva", "Missouri", "Molly", + "Mordecai", "Morgan", "Morris", "Myrtle", "Nancy", "Natalie", "Nathaniel", "Ned", + "Nellie", "Nettie", "Newton", "Nicholas", "Nimrod", "Ninian", "Nora", "Obediah", + "Octavius", "Orpha", "Orville", "Oscar", "Owen", "Parthena", "Patrick", "Patrick Henry", + "Patsy", "Paul", "Paul", "Peggy", "Permelia", "Perry", "Peter", "Philomena", + "Phoebe", "Pleasant", "Polly", "Preshea", "Rachel", "Ralph", "Raymond", "Rebecca", + "Reuben", "Rhoda", "Richard", "Robert", "Robert Lee", "Roderick", "Rowena", "Rudolph", + "Rufina", "Rufus", "Ruth", "Sally", "Sam Houston", "Samantha", "Samuel", "Sarah", + "Sarah Ann", "Sarah Elizabeth", "Savannah", "Selina", "Seth", "Silas", "Simeon", "Simon", + "Sophronia", "Stanley", "Stella", "Stephen", "Thaddeus", "Theodore", "Theodosia", "Thomas", + "Timothy", "Ulysses", "Uriah", "Vertiline", "Victor", "Victoria", "Virginia", "Vivian", + "Walter", "Warren", "Washington", "Wilfred", "William", "Winnifred", "Zachariah", "Zebulon", + "Zedock", "Zona", "Zylphia" + }; + + public static final String[] ENTITY_LAST_NAMES = { + "Abraham", "Adams", "Alcorn", "Alderdice", "Angus", "Ashdown", "Ayre", "Backhaus", + "Baldwin", "Bamford", "Beaton", "Blackwood", "Blair", "Blewett", "Bornholdt", "Bowden", + "Burrows", "Cameron", "Carroll", "Clarke", "Claxton", "Collins", "Colson", "Connor", + "Conroy", "Cullen", "Cunningham", "Curd", "Curnow", "Cusack", "Dagon", "Dalton", + "Dawes", "Desmond", "Dewar", "Dickenson", "Donnell", "Drummond", "Dunstan", "English", + "Eveans", "Faraday", "Faulkner", "Fitzgerald", "Fitzpatrick", "Fletcher", "Foster", "Franklin", + "Fulton", "Gallagher", "Gibbons", "Gilmore", "Glover", "Goodfellow", "Goodwin", "Griffiths", + "Gullifer", "Hadley", "Haeffner", "Hanlon", "Harding", "Harris", "Holloway", "Hughes", + "Jarvis", "Jefferies", "Johnstone", "Kaylock", "Keane", "Kemp", "Kernaghan", "Kirby", + "Kirkland", "Knight", "LaFontaine", "Lawford", "Lawrence", "Lennox", "Longley", "Lonsdale", + "Luckett", "Lyons", "Macklin", "Madill", "Marsden", "Marshall", "Martin", "Mather", + "Mathieson", "Maunder", "McColl", "McDermott", "McGillicuddy", "McKenzie", "McLachlan", "McNeil", + "Meaklim", "Meighan", "Mellor", "Meyers", "Milsom", "Mitchell", "Mitchelson", "Moore", + "Morgan", "Morrison", "Mortimer", "Moulsdale", "Murphy", "Nelson", "Nolan", "Noonan", + "O'Keefe", "O'Sullivan", "Palmer", "Parnell", "Pattison", "Pettit", "Phillips", "Pinner", + "Porter", "Prosser", "Ramseyer", "Renton", "Rickard", "Riddington", "Roche", "Rowe", + "Russell", "Salisbury", "Saunders", "Sawyer", "Scanlan", "Scarborough", "Schwarer", "Sheary", + "Sheedy", "Shelton", "Shields", "Shinnick", "Skinner", "Sommer", "Spencer", "Stanbury", + "Stanton", "Storey", "Swaisbrick", "Thorley", "Thumpston", "Tichborne", "Tinning", "Tobin", + "Todd", "Trimble", "Twomey", "Upton", "Urwin", "Vandenburg", "Vinge", "Wakefield", + "Wakenshaw", "Walden", "Wallace", "Walton", "Warner", "Webb", "Whitehill", "Wickes", + "Wilberforce", "Wilkinson", "Wolstenholme", "Wright" + }; + + public static String getRandomZoneName() { + return getRandomName(ZONE_FIRST_NAMES, ZONE_LAST_NAMES); + } + + public static String getRandomEntityName() { + return getRandomName(ENTITY_FIRST_NAMES, ENTITY_LAST_NAMES); + } + + private static String getRandomName(String[] firstNames, String[] lastNames) { + String firstName = firstNames[(int)(Math.random() * firstNames.length)]; + String lastName = lastNames[(int)(Math.random() * lastNames.length)]; + return firstName + " " + lastName; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java index 9700335a..43124bf4 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java @@ -59,15 +59,7 @@ public void tick(float deltaTime) { long now = System.currentTimeMillis(); // Update block position - lastBlockX = blockX; - lastBlockY = blockY; - blockX = (int)x; - blockY = (int)y; - - // Check if block position has changed - if(lastBlockX != blockX || lastBlockY != blockY) { - blockPositionChanged(); - } + updateBlockPosition(); // Clear expired recent attacks recentAttacks.removeIf(attack -> now >= attack.getTime() + ATTACK_RETENTION_TIME); @@ -129,6 +121,18 @@ public float getDefense(EntityAttack attack) { return 1.0F; // Override } + public void updateBlockPosition() { + lastBlockX = blockX; + lastBlockY = blockY; + blockX = (int)x; + blockY = (int)y; + + // Check if block position has changed + if(lastBlockX != blockX || lastBlockY != blockY) { + blockPositionChanged(); + } + } + public void blockPositionChanged() { // Check for touchplates if(zone != null && zone.isChunkLoaded(blockX, blockY)) { @@ -263,6 +267,7 @@ public float getHealth() { public void setPosition(float x, float y) { this.x = x; this.y = y; + updateBlockPosition(); } public float getX() { diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java b/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java index 9fec3615..0da885c5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java @@ -29,6 +29,8 @@ public class EntityConfig { private int experienceYield; private float maxHealth = Entity.DEFAULT_HEALTH; private float baseSpeed = 3; + private boolean character; + private boolean named; private Vector2i size = new Vector2i(1, 1); private EntityGroup group = EntityGroup.NONE; private WeightedMap loot = new WeightedMap<>(); @@ -79,6 +81,14 @@ public float getBaseSpeed() { return baseSpeed; } + public boolean isCharacter() { + return character; + } + + public boolean isNamed() { + return named; + } + @JsonSetter(nulls = Nulls.SKIP) private void setSize(Vector2i size) { this.size = size; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java index eacd0f14..5989a5f5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java @@ -9,6 +9,7 @@ import java.util.Map.Entry; import java.util.concurrent.ThreadLocalRandom; +import brainwine.gameserver.Naming; import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.EntityAttack; import brainwine.gameserver.entity.EntityConfig; @@ -32,6 +33,7 @@ public class Npc extends Entity { private final String typeName; private final float maxHealth; private final float baseSpeed; + private final boolean persist; private final Vector2i size; private final WeightedMap loot; private final WeightedMap placedLoot; @@ -98,11 +100,17 @@ public Npc(Zone zone, EntityConfig config) { properties.put("sl", slots); } + // Generate random name + if(config.isNamed()) { + this.name = Naming.getRandomEntityName(); + } + this.config = config; this.typeName = config.getName(); this.type = config.getType(); this.maxHealth = config.getMaxHealth(); this.baseSpeed = config.getBaseSpeed(); + this.persist = config.isCharacter(); this.size = config.getSize(); this.loot = config.getLoot(); this.placedLoot = config.getPlacedLoot(); @@ -267,7 +275,7 @@ public void setDefense(DamageType type, float amount) { } public boolean isTransient() { - return !isGuard() && !isMounted(); + return !isGuard() && !isMounted() && !persist; } public EntityLoot getRandomLoot(Player awardee, Item weapon) { @@ -368,6 +376,10 @@ public boolean isArtificial() { return artificial; } + public boolean isPersistent() { + return persist; + } + public void setSpeed(float speed) { this.speed = speed; } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/NpcData.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/NpcData.java new file mode 100644 index 00000000..1d173303 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/NpcData.java @@ -0,0 +1,47 @@ +package brainwine.gameserver.entity.npc; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import brainwine.gameserver.entity.EntityConfig; + +/** + * Storage data for persistent non-player characters. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class NpcData { + + private EntityConfig type; + private String name; + private int x; + private int y; + + @JsonCreator + public NpcData(@JsonProperty(value = "type", required = true) EntityConfig type) { + this.type = type; + } + + public NpcData(Npc npc) { + this.type = npc.getConfig(); + this.name = npc.getName(); + this.x = npc.getBlockX(); + this.y = npc.getBlockY(); + } + + public EntityConfig getType() { + return type; + } + + public String getName() { + return name; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java index 35ec8605..8b7ac854 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java @@ -20,12 +20,12 @@ import com.fasterxml.jackson.core.type.TypeReference; -import brainwine.gameserver.GameServer; import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.EntityConfig; import brainwine.gameserver.entity.EntityRegistry; import brainwine.gameserver.entity.EntityStatus; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.NpcData; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; @@ -175,8 +175,8 @@ private boolean tryBustOrifice(int x, int y, Layer layer) { private void clearEntities() { npcs.values().stream() - .filter(npc -> npc.isDead() || !zone.isChunkLoaded((int)npc.getX(), (int)npc.getY()) || - (npc.isTransient() && System.currentTimeMillis() > npc.getLastTrackedAt() + ENTITY_CLEAR_TIME)) + .filter(npc -> npc.isDead() || (!npc.isPersistent() && (!zone.isChunkLoaded(npc.getBlockX(), npc.getBlockY()) || + (npc.isTransient() && System.currentTimeMillis() > npc.getLastTrackedAt() + ENTITY_CLEAR_TIME)))) .collect(Collectors.toList()) .forEach(this::removeEntity); } @@ -249,18 +249,28 @@ public void trySpawnBlockEntity(int x, int y) { } } + public void spawnPersistentNpcs(Collection data) { + for(NpcData entry : data) { + if(entry.getType() == null) { + continue; + } + + Npc npc = new Npc(zone, entry.getType()); + npc.setName(entry.getName()); + spawnEntity(npc, entry.getX(), entry.getY()); + } + } + public void spawnEntity(Entity entity, int x, int y) { spawnEntity(entity, x, y, false); } public void spawnEntity(Entity entity, int x, int y, boolean effect) { - if(zone.isChunkLoaded(x, y)) { - addEntity(entity); - entity.setPosition(x, y); - - if(effect) { - zone.sendMessageToChunk(new EffectMessage(x + 0.5F, y + 0.5F, "bomb-teleport", 4), zone.getChunk(x, y)); - } + addEntity(entity); + entity.setPosition(x, y); + + if(effect && zone.isChunkLoaded(x, y)) { + zone.sendMessageToChunk(new EffectMessage(x + 0.5F, y + 0.5F, "bomb-teleport", 4), zone.getChunk(x, y)); } } @@ -332,13 +342,17 @@ public int getNpcCount() { } public int getTransientNpcCount() { - return (int)npcs.values().stream().filter(npc -> npc.isTransient()).count(); + return (int)npcs.values().stream().filter(Npc::isTransient).count(); } public Collection getNpcs() { return Collections.unmodifiableCollection(npcs.values()); } + public List getPersistentNpcs() { + return npcs.values().stream().filter(Npc::isPersistent).collect(Collectors.toList()); + } + public Player getPlayer(int entityId) { return players.get(entityId); } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 6ced37e4..0ce28ce8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -25,6 +25,7 @@ import brainwine.gameserver.Timer; import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.NpcData; import brainwine.gameserver.entity.player.ChatType; import brainwine.gameserver.entity.player.NotificationType; import brainwine.gameserver.entity.player.Player; @@ -1095,6 +1096,10 @@ public List getPlayersInRange(float x, float y, float range) { return entityManager.getPlayersInRange(x, y, range); } + public void spawnPersistentNpcs(Collection data) { + entityManager.spawnPersistentNpcs(data); + } + public void spawnEntity(Entity entity, int x, int y) { entityManager.spawnEntity(entity, x, y); } @@ -1135,6 +1140,10 @@ public Collection getNpcs() { return entityManager.getNpcs(); } + public List getPersistentNpcs() { + return entityManager.getPersistentNpcs(); + } + public Player getPlayer(int entityId) { return entityManager.getPlayer(entityId); } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java index 4398e731..bfcac9d0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Map; import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.zip.DataFormatException; import org.apache.logging.log4j.LogManager; @@ -24,6 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import brainwine.gameserver.entity.npc.NpcData; import brainwine.gameserver.util.ZipUtils; import brainwine.gameserver.zone.gen.ZoneGenerator; import brainwine.shared.JsonHelper; @@ -84,6 +86,9 @@ private void loadZone(File file) { String id = file.getName(); File dataFile = new File(file, "zone.dat"); File legacyDataFile = new File(file, "shape.cmp"); + File configFile = new File(file, "config.json"); + File metaBlocksFile = new File(file, "metablocks.json"); + File charactersFile = new File(file, "characters.json"); try { ZoneDataFile data = null; @@ -95,9 +100,19 @@ private void loadZone(File file) { data = mapper.readValue(ZipUtils.inflateBytes(Files.readAllBytes(dataFile.toPath())), ZoneDataFile.class); } - ZoneConfigFile config = JsonHelper.readValue(new File(file, "config.json"), ZoneConfigFile.class); + ZoneConfigFile config = JsonHelper.readValue(configFile, ZoneConfigFile.class); Zone zone = new Zone(id, config, data); - zone.setMetaBlocks(JsonHelper.readList(new File(file, "metablocks.json"), MetaBlock.class)); + + // Load meta blocks + if(metaBlocksFile.exists()) { + zone.setMetaBlocks(JsonHelper.readList(metaBlocksFile, MetaBlock.class)); + } + + // Load characters + if(charactersFile.exists()) { + zone.spawnPersistentNpcs(JsonHelper.readList(charactersFile, NpcData.class)); + } + addZone(zone); } catch (Exception e) { logger.error(SERVER_MARKER, "Zone load failure. id: {}", id, e); @@ -147,10 +162,18 @@ public void saveZone(Zone zone) { file.mkdirs(); try { + // Serialize everything before writing to disk to minimize risk of data corruption if something goes wrong + byte[] charactersBytes = JsonHelper.writeValueAsBytes(zone.getPersistentNpcs().stream().map(NpcData::new).collect(Collectors.toList())); + byte[] metaBlocksBytes = JsonHelper.writeValueAsBytes(zone.getMetaBlocks()); + byte[] configBytes = JsonHelper.writeValueAsBytes(new ZoneConfigFile(zone)); + byte[] dataBytes = ZipUtils.deflateBytes(mapper.writeValueAsBytes(new ZoneDataFile(zone))); + + // Write data to files zone.saveChunks(); - JsonHelper.writeValue(new File(file, "metablocks.json"), zone.getMetaBlocks()); - JsonHelper.writeValue(new File(file, "config.json"), new ZoneConfigFile(zone)); - Files.write(new File(file, "zone.dat").toPath(), ZipUtils.deflateBytes(mapper.writeValueAsBytes(new ZoneDataFile(zone)))); + Files.write(new File(file, "characters.json").toPath(), charactersBytes); + Files.write(new File(file, "metablocks.json").toPath(), metaBlocksBytes); + Files.write(new File(file, "config.json").toPath(), configBytes); + Files.write(new File(file, "zone.dat").toPath(), dataBytes); } catch(Exception e) { logger.error(SERVER_MARKER, "Zone save failure. id: {}", zone.getDocumentId(), e); } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java index 0daaa6de..542a5267 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java @@ -13,6 +13,7 @@ import org.apache.logging.log4j.Logger; import brainwine.gameserver.GameServer; +import brainwine.gameserver.Naming; import brainwine.gameserver.item.Layer; import brainwine.gameserver.util.ResourceUtils; import brainwine.gameserver.zone.Biome; @@ -26,34 +27,6 @@ public class ZoneGenerator { - // TODO Collect more names and create a name generator that's actually proper lmao - private static final String[] FIRST_NAMES = { - "Malvern", "Tralee", "Horncastle", "Old", "Westwood", - "Citta", "Tadley", "Mossley", "West", "East", - "North", "South", "Wadpen", "Githam", "Soatnust", - "Highworth", "Creakynip", "Upper", "Lower", "Cannock", - "Dovercourt", "Limerick", "Pickering", "Glumshed", "Crusthack", - "Osyltyr", "Aberstaple", "New", "Stroud", "Crumclum", - "Crumsidle", "Bankswund", "Fiddletrast", "Bournpan", "St.", - "Funderbost", "Bexwoddly", "Pilkingheld", "Wittlepen", "Rabbitbleaker", - "Griffingumby", "Guilthead", "Bigglelund", "Bunnymold", "Rosesidle", - "Crushthorn", "Tanlyward", "Ahncrace", "Pilkingking", "Dingstrath", - "Axebury", "Ginglingtap", "Ballybibby", "Shadehoven" - }; - - private static final String[] LAST_NAMES = { - "Falls", "Alloa", "Glen", "Way", "Dolente", - "Peak", "Heights", "Creek", "Banffshire", "Chagford", - "Gorge", "Valley", "Catacombs", "Depths", "Mines", - "Crickbridge", "Guildbost", "Pits", "Vaults", "Ruins", - "Dell", "Keep", "Chatterdin", "Scrimmance", "Gitwick", - "Ridge", "Alresford", "Place", "Bridge", "Glade", - "Mill", "Court", "Dooftory", "Hills", "Specklewint", - "Grove", "Aylesbury", "Wagwouth", "Russetcumby", "Point", - "Canyon", "Cranwarry", "Bluff", "Passage", "Crantippy", - "Kerbodome", "Dale", "Cemetery" - }; - private static final Logger logger = LogManager.getLogger(); private static final Map generators = new HashMap<>(); private static final ZoneGenerator defaultGenerator = new ZoneGenerator(); @@ -158,7 +131,7 @@ public Zone generateZone(Biome biome, int width, int height) { public Zone generateZone(Biome biome, int width, int height, int seed) { String id = generateDocumentId(seed); - String name = getRandomName(); + String name = Naming.getRandomZoneName(); int retryCount = 0; while(GameServer.getInstance().getZoneManager().getZoneByName(name) != null) { @@ -168,7 +141,7 @@ public Zone generateZone(Biome biome, int width, int height, int seed) { break; } - name = getRandomName(); + name = Naming.getRandomZoneName(); retryCount++; } @@ -210,12 +183,6 @@ private static String generateDocumentId(int seed) { return new UUID(mostSigBits, leastSigBits).toString(); } - private static String getRandomName() { - String firstName = FIRST_NAMES[(int)(Math.random() * FIRST_NAMES.length)]; - String lastName = LAST_NAMES[(int)(Math.random() * LAST_NAMES.length)]; - return firstName + " " + lastName; - } - private static int getRandomSeed() { return (int)(Math.random() * Integer.MAX_VALUE); } From 20a9b5b8268991f0ebb9033a55bbe3de8fb6491e Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 18 Feb 2024 22:05:04 +0100 Subject: [PATCH 019/176] Remove unused import --- .../gameserver/entity/npc/behavior/parts/ShielderBehavior.java | 1 - 1 file changed, 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ShielderBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ShielderBehavior.java index 53757436..664fd2c5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ShielderBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ShielderBehavior.java @@ -2,7 +2,6 @@ import java.util.Arrays; import java.util.HashSet; -import java.util.List; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; From 38cf04187060445c67f5406a570cf8e50cd2fbbb Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 18 Feb 2024 23:51:50 +0100 Subject: [PATCH 020/176] Add a bunch of utility functions --- .../brainwine/gameserver/entity/Entity.java | 27 ++++++++++++++++ .../brainwine/gameserver/entity/npc/Npc.java | 16 ++++------ .../behavior/parts/SpawnAttackBehavior.java | 6 ++-- .../npc/behavior/parts/UnblockBehavior.java | 6 ++-- .../gameserver/entity/player/Player.java | 4 +-- .../item/interactions/SwitchInteraction.java | 12 +++---- .../server/requests/BlockMineRequest.java | 3 +- .../gameserver/zone/EntityManager.java | 3 +- .../java/brainwine/gameserver/zone/Zone.java | 31 +++++++++++++++---- 9 files changed, 70 insertions(+), 38 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java index 43124bf4..5efa62a3 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java @@ -12,6 +12,7 @@ import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.messages.EffectMessage; import brainwine.gameserver.server.messages.EntityChangeMessage; import brainwine.gameserver.server.messages.EntityStatusMessage; import brainwine.gameserver.util.MapHelper; @@ -45,6 +46,8 @@ public abstract class Entity { protected int lastBlockY; protected int targetX; protected int targetY; + protected int sizeX = 1; + protected int sizeY = 1; protected FacingDirection direction = FacingDirection.WEST; protected int animation; protected boolean invulnerable; @@ -121,6 +124,22 @@ public float getDefense(EntityAttack attack) { return 1.0F; // Override } + public void spawnEffect(String type) { + spawnEffect(type, 1); + } + + public void spawnEffect(String type, Object data) { + float effectX = x + sizeX / 2.0F; + float effectY = y + sizeY / 2.0F; + sendMessageToTrackers(new EffectMessage(effectX, effectY, type, data)); + } + + public void emote(String message) { + float effectX = x + sizeX / 2.0F; + float effectY = y - sizeY + 1; + sendMessageToTrackers(new EffectMessage(effectX, effectY, "emote", message)); + } + public void updateBlockPosition() { lastBlockX = blockX; lastBlockY = blockY; @@ -312,6 +331,14 @@ public int getBlockY() { return blockY; } + public int getSizeX() { + return sizeX; + } + + public int getSizeY() { + return sizeY; + } + public void setDirection(FacingDirection direction) { this.direction = direction; } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java index 5989a5f5..1b931899 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java @@ -34,7 +34,6 @@ public class Npc extends Entity { private final float maxHealth; private final float baseSpeed; private final boolean persist; - private final Vector2i size; private final WeightedMap loot; private final WeightedMap placedLoot; private final Map> lootByWeapon; @@ -111,7 +110,8 @@ public Npc(Zone zone, EntityConfig config) { this.maxHealth = config.getMaxHealth(); this.baseSpeed = config.getBaseSpeed(); this.persist = config.isCharacter(); - this.size = config.getSize(); + this.sizeX = config.getSize().getX(); + this.sizeY = config.getSize().getY(); this.loot = config.getLoot(); this.placedLoot = config.getPlacedLoot(); this.lootByWeapon = config.getLootByWeapon(); @@ -262,10 +262,6 @@ public EntityConfig getConfig() { return config; } - public Vector2i getSize() { - return size; - } - public void setDefense(DamageType type, float amount) { if(amount == 0) { activeDefenses.remove(type); @@ -411,15 +407,15 @@ public boolean isBlocked(int oX, int oY) { int tY = y + oY; boolean blocked = zone.isBlockSolid(tX, tY) || (oX != 0 && zone.isBlockSolid(tX, y)) || (oY != 0 && zone.isBlockSolid(x, tY)); - if(size.getX() > 1) { - int additionalWidth = size.getX() - 1; + if(sizeX > 1) { + int additionalWidth = sizeX - 1; blocked = blocked || zone.isBlockSolid(tX + additionalWidth, tY) || (oX != 0 && zone.isBlockSolid(tX + additionalWidth, y)) || (oY != 0 && zone.isBlockSolid(x + additionalWidth, tY)); } - if(size.getY() > 1) { - int additionalHeight = size.getY() - 1; + if(sizeY > 1) { + int additionalHeight = sizeY - 1; blocked = blocked || zone.isBlockSolid(tX, tY - additionalHeight) || (oX != 0 && zone.isBlockSolid(tX, y - additionalHeight)) || (oY != 0 && zone.isBlockSolid(x, tY - additionalHeight)); diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/SpawnAttackBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/SpawnAttackBehavior.java index 4c8d615f..9871ad3c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/SpawnAttackBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/SpawnAttackBehavior.java @@ -12,7 +12,6 @@ import brainwine.gameserver.entity.npc.behavior.Behavior; import brainwine.gameserver.server.messages.EntityStatusMessage; import brainwine.gameserver.util.MapHelper; -import brainwine.gameserver.util.Vector2i; public class SpawnAttackBehavior extends Behavior { @@ -41,9 +40,8 @@ public boolean behave() { if(npc) { // Spawn child at parent's location - Vector2i size = entity.getSize(); - int spawnX = (int)(entity.getX() + (size.getX() / 2F)); - int spawnY = (int)(entity.getY() + (size.getY() / 2F)); + int spawnX = (int)(entity.getX() + (entity.getSizeX() / 2.0F)); + int spawnY = (int)(entity.getY() + (entity.getSizeX() / 2.0F)); Npc child = new Npc(entity.getZone(), entityConfig); child.setOwner(entity); entity.addChild(child); diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/UnblockBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/UnblockBehavior.java index e1615b00..8c40083a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/UnblockBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/UnblockBehavior.java @@ -8,7 +8,6 @@ import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.entity.npc.behavior.Behavior; -import brainwine.gameserver.util.Vector2i; import brainwine.gameserver.zone.Zone; public class UnblockBehavior extends Behavior { @@ -23,12 +22,11 @@ public UnblockBehavior(@JacksonInject Npc entity) { @Override public boolean behave() { Zone zone = entity.getZone(); - Vector2i size = entity.getSize(); Random random = ThreadLocalRandom.current(); for(int i = 0; i < rate; i++) { - int x = (int)entity.getX() + random.nextInt(size.getX()); - int y = (int)entity.getY() - random.nextInt(size.getY()); + int x = (int)entity.getX() + random.nextInt(entity.getSizeX()); + int y = (int)entity.getY() - random.nextInt(entity.getSizeY()); if(zone.isChunkLoaded(x, y) && zone.getBlock(x, y).getFrontItem().isDiggable()) { zone.digBlock(x, y); diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java index 60f78bc5..fc0b0587 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java @@ -546,7 +546,7 @@ public void respawn() { sendMessage(new PlayerPositionMessage(spawnX, spawnY)); sendMessageToPeers(new EntityStatusMessage(this, EntityStatus.REVIVED)); - zone.sendMessage(new EffectMessage(spawnX, spawnY, "spawn", 20)); + zone.spawnEffect(spawnX, spawnY, "spawn", 20); } /** @@ -561,7 +561,7 @@ public void teleport(int x, int y) { teleportY = y; sendMessage(new TeleportMessage(x, y)); sendMessage(new PlayerPositionMessage(x, y)); - zone.sendMessage(new EffectMessage(x, y, "teleport", 20)); + zone.spawnEffect(x, y, "teleport", 20); } public int getTeleportX() { diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java index 3bf963b1..a7575043 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java @@ -16,11 +16,8 @@ import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; -import brainwine.gameserver.server.messages.BlockMetaMessage; -import brainwine.gameserver.server.messages.EffectMessage; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.Vector2i; -import brainwine.gameserver.zone.Chunk; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; @@ -50,7 +47,7 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i if(message != null && !message.isEmpty()) { float effectX = x + item.getBlockWidth() / 2.0F; float effectY = y - item.getBlockHeight() + 1; - zone.sendMessageToChunk(new EffectMessage(effectX, effectY, "emote", message), zone.getChunk(x, y)); + zone.spawnEffect(effectX, effectY, "emote", message); } // Prepare list of targets @@ -116,8 +113,8 @@ private void switchSpawner(Zone zone, MetaBlock metaBlock) { Entity entity = zone.getEntity(metaBlock.getIntProperty("eid")); if(entity != null && !entity.isDead()) { + entity.spawnEffect("bomb-teleport", 4); entity.setHealth(0); - zone.sendMessageToChunk(new EffectMessage(entity.getX(), entity.getY(), "bomb-teleport", 4), zone.getChunk(metaBlock.getX(), metaBlock.getY())); } } @@ -207,8 +204,7 @@ private void switchSign(Zone zone, Entity entity, MetaBlock metaBlock, MetaBlock // Send data to players float effectX = metaBlock.getX() + (float)item.getBlockWidth() / 2; float effectY = metaBlock.getY() - (float)item.getBlockHeight() / 2 + 1; - Chunk chunk = zone.getChunk(metaBlock.getX(), metaBlock.getY()); - zone.sendMessageToChunk(new EffectMessage(effectX, effectY, "area steam", 10), chunk); - zone.sendMessageToChunk(new BlockMetaMessage(metaBlock), chunk); + zone.spawnEffect(effectX, effectY, "area steam", 10); + zone.sendBlockMetaUpdate(metaBlock); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index 3c80eecc..349afbf6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -21,7 +21,6 @@ import brainwine.gameserver.item.ModType; import brainwine.gameserver.server.PlayerRequest; import brainwine.gameserver.server.messages.BlockChangeMessage; -import brainwine.gameserver.server.messages.EffectMessage; import brainwine.gameserver.server.messages.InventoryMessage; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.MathUtils; @@ -194,8 +193,8 @@ private void destroySpawner(Zone zone, MetaBlock metaBlock) { // Kill entity if it exists if(entity != null && !entity.isDead()) { + entity.spawnEffect("bomb-teleport", 4); entity.setHealth(0); - zone.sendMessageToChunk(new EffectMessage(entity.getX(), entity.getY(), "bomb-teleport", 4), zone.getChunk(metaBlock.getX(), metaBlock.getY())); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java index 8b7ac854..9e7a0999 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java @@ -30,7 +30,6 @@ import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; import brainwine.gameserver.item.ModType; -import brainwine.gameserver.server.messages.EffectMessage; import brainwine.gameserver.server.messages.EntityPositionMessage; import brainwine.gameserver.server.messages.EntityStatusMessage; import brainwine.gameserver.util.MapHelper; @@ -270,7 +269,7 @@ public void spawnEntity(Entity entity, int x, int y, boolean effect) { entity.setPosition(x, y); if(effect && zone.isChunkLoaded(x, y)) { - zone.sendMessageToChunk(new EffectMessage(x + 0.5F, y + 0.5F, "bomb-teleport", 4), zone.getChunk(x, y)); + zone.spawnEffect(x + 0.5F, y + 0.5F, "bomb-teleport", 4); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 0ce28ce8..4fc21e65 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -186,7 +186,7 @@ public void sendMessage(Message message) { * @param message The message to send. * @param chunk The chunk near which players must be. */ - public void sendMessageToChunk(Message message, Chunk chunk) { + public void sendLocalMessage(Message message, Chunk chunk) { for(Player player : getPlayers()) { if(player.isChunkActive(chunk)) { player.sendMessage(message); @@ -194,6 +194,22 @@ public void sendMessageToChunk(Message message, Chunk chunk) { } } + public void sendLocalMessage(Message message, float x, float y) { + sendLocalMessage(message, (int)x, (int)y); + } + + public void sendLocalMessage(Message message, int x, int y) { + if(!isChunkLoaded(x, y)) { + return; + } + + sendLocalMessage(message, getChunk(x, y)); + } + + public void sendBlockMetaUpdate(MetaBlock metaBlock) { + sendLocalMessage(new BlockMetaMessage(metaBlock), metaBlock.getX(), metaBlock.getY()); + } + public void sendChatMessage(Player sender, String text) { sendChatMessage(sender, text, ChatType.CHAT); } @@ -210,6 +226,10 @@ public void sendChatMessage(Player sender, String text, ChatType type) { GameServer.getInstance().notify(String.format("%s: %s", sender.getName(), text), NotificationType.CHAT); } + public void spawnEffect(float x, float y, String type, Object data) { + sendLocalMessage(new EffectMessage(x, y, type, data), x, y); + } + public boolean isPointVisibleFrom(int x1, int y1, int x2, int y2) { return raycast(x1, y1, x2, y2) == null; } @@ -304,7 +324,7 @@ public void explode(int x, int y, float radius, Entity cause, boolean destructiv return; } - sendMessageToChunk(new EffectMessage(x + 0.5f, y + 0.5f, effect, radius), getChunk(x, y)); + spawnEffect(x + 0.5F, y + 0.5F, effect, radius); Player player = cause instanceof Player ? (Player)cause : null; Item item = getBlock(x, y).getFrontItem(); @@ -924,7 +944,7 @@ public void updateBlock(int x, int y, Layer layer, Item item, int mod, Player ow recalculateSunlight(x, sunlight[x]); } - sendMessageToChunk(new LightMessage(x, getSunlight(x, 1)), chunk); + sendLocalMessage(new LightMessage(x, getSunlight(x, 1)), chunk); } else if(layer == Layer.LIQUID) { if(!item.isAir() && mod > 0) { liquidManager.indexLiquidBlock(x, y); @@ -990,8 +1010,7 @@ public void setMetaBlock(int x, int y, Item item, Player owner, Map Date: Tue, 20 Feb 2024 01:58:25 +0100 Subject: [PATCH 021/176] Implement steam system --- .../java/brainwine/gameserver/item/Item.java | 7 + .../server/requests/CraftRequest.java | 39 ++- .../gameserver/zone/SteamIteration.java | 35 +++ .../gameserver/zone/SteamManager.java | 227 ++++++++++++++++++ .../java/brainwine/gameserver/zone/Zone.java | 15 +- .../gameserver/zone/ZoneDataFile.java | 12 +- .../gameserver/zone/ZoneManager.java | 2 +- 7 files changed, 323 insertions(+), 14 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/zone/SteamIteration.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/zone/SteamManager.java diff --git a/gameserver/src/main/java/brainwine/gameserver/item/Item.java b/gameserver/src/main/java/brainwine/gameserver/item/Item.java index 2014db19..6e3a7ec8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/Item.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/Item.java @@ -120,6 +120,9 @@ public class Item { @JsonProperty("entity") private boolean entity; + @JsonProperty("steam") + private boolean steam; + @JsonProperty("inventory") private LazyItemGetter inventoryItem; @@ -390,6 +393,10 @@ public boolean isEntity() { return entity; } + public boolean usesSteam() { + return steam; + } + public Map getSkillBonuses() { return skillBonuses; } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/CraftRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/CraftRequest.java index 4e4f9eab..b5f9454e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/CraftRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/CraftRequest.java @@ -1,6 +1,7 @@ package brainwine.gameserver.server.requests; import java.util.List; +import java.util.stream.Collectors; import brainwine.gameserver.annotations.OptionalField; import brainwine.gameserver.annotations.RequestInfo; @@ -13,10 +14,8 @@ import brainwine.gameserver.util.MathUtils; import brainwine.gameserver.util.Pair; import brainwine.gameserver.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; -/** - * TODO Account for skills, bonuses etc.. - */ @RequestInfo(id = 19) public class CraftRequest extends PlayerRequest { @@ -54,21 +53,43 @@ public void process(Player player) { return; } } - + // Check if required crafting helpers are nearby if(!player.isGodMode() && item.requiresWorkshop()) { - List workshop = player.getZone().getMetaBlocks(metaBlock - -> MathUtils.inRange(player.getX(), player.getY(), metaBlock.getX(), metaBlock.getY(), 10)); + Zone zone = player.getZone(); + + // Fetch list of all meta blocks in the player's vicinity + List workshop = zone.getMetaBlocks(metaBlock -> zone.isChunkLoaded(metaBlock.getX(), metaBlock.getY()) + && MathUtils.inRange(player.getX(), player.getY(), metaBlock.getX(), metaBlock.getY(), 20)); + // Check for each crafting helper if it is present in the workshop and available for use for(CraftingRequirement craftingHelper : item.getCraftingHelpers()) { - int quantityMissing = craftingHelper.getQuantity() - (int)workshop.stream().filter(metaBlock - -> metaBlock.getItem() == craftingHelper.getItem()).count(); + int quantityRequired = craftingHelper.getQuantity(); + // Fetch list of crafting helpers of this type that are present in the workshop + List presentCraftingHelpers = workshop.stream() + .filter(metaBlock -> metaBlock.getItem() == craftingHelper.getItem()).collect(Collectors.toList()); + int quantityMissing = quantityRequired - presentCraftingHelpers.size(); + + // Check if workshop is still missing crafting helpers of this type and notify the player if this is the case if(quantityMissing > 0) { - player.notify(String.format("You can't craft this item because your workshop is lacking %sx %s.", + player.notify(String.format("You can't craft this item because your workshop is still lacking %sx %s.", quantityMissing, craftingHelper.getItem().getTitle())); return; } + + // Perform additional checks if the crafting helper requires steam to function + if(craftingHelper.getItem().usesSteam()) { + quantityMissing = quantityRequired - (int)presentCraftingHelpers.stream() + .filter(metaBlock -> zone.getBlock(metaBlock.getX(), metaBlock.getY()).getFrontMod() == 1).count(); + + // Notify the player if not enough crafting helpers are powered + if(quantityMissing > 0) { + player.notify(String.format("You can't craft this item because your workshop still needs to provide steam power to %sx %s.", + quantityMissing, craftingHelper.getItem().getTitle())); + return; + } + } } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/SteamIteration.java b/gameserver/src/main/java/brainwine/gameserver/zone/SteamIteration.java new file mode 100644 index 00000000..67280438 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/zone/SteamIteration.java @@ -0,0 +1,35 @@ +package brainwine.gameserver.zone; + +/** + * Used by {@link SteamManager} to keep track of things. + */ +public class SteamIteration { + + private int x; + private int y; + private byte direction; + private short depth; + + public SteamIteration(int x, int y, int direction, int depth) { + this.x = x; + this.y = y; + this.direction = (byte)direction; + this.depth = (short)depth; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public byte getDirection() { + return direction; + } + + public short getDepth() { + return depth; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/SteamManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/SteamManager.java new file mode 100644 index 00000000..cc86858d --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/zone/SteamManager.java @@ -0,0 +1,227 @@ +package brainwine.gameserver.zone; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Queue; +import java.util.Set; + +import brainwine.gameserver.item.Item; + +/** + * Distributes steam through collectors to nearby machines via pipes. + */ +public class SteamManager { + + public static final int STEAM_UPDATE_INTERVAL = 3000; // Update interval in milliseconds + public static final int MAX_ITERATIONS = 300; // Maximum number of iterations before giving up + public static final int MAX_COLLECTOR_DISTANCE = 350; // Collectors that are not within this distance of any players in the zone will be skipped + public static final byte STATE_EMPTY = 0x0; // Nothing or unrelated + public static final byte STATE_PIPE = 0x1; // Pipe + public static final byte STATE_COLLECTOR = 0x2; // Active collector + private final Set collectorIndices = new HashSet<>(); + private final Set steamableIndices = new HashSet<>(); + private final Set processedIndices = new HashSet<>(); + private final List expiredSteamableIndices = new ArrayList<>(); + private final Queue processQueue = new ArrayDeque<>(); + private final Zone zone; + private byte[] data; + private long lastUpdateAt; + + public SteamManager(Zone zone) { + this.zone = zone; + this.data = new byte[(zone.getWidth() * zone.getHeight()) >> 2]; + } + + public void tick(double deltaTime) { + long now = System.currentTimeMillis(); + + // Check if it's time to update steam yet + if(now > lastUpdateAt + STEAM_UPDATE_INTERVAL) { + updateSteam(); + lastUpdateAt = now; + } + } + + private void updateSteam() { + // Do nothing if there are no players in this zone + if(zone.getPlayerCount() == 0) { + return; + } + + // Clear data from previous run + processedIndices.clear(); + expiredSteamableIndices.clear(); + + // Turn off all steam-powered objects + for(int index : steamableIndices) { + int x = index % zone.getWidth(); + int y = index / zone.getWidth(); + + // Skip if chunk isn't loaded + if(!zone.isChunkLoaded(x, y)) { + expiredSteamableIndices.add(index); + continue; + } + + Item item = zone.getBlock(x, y).getFrontItem(); + + // Skip if front item doesn't use steam + if(!item.usesSteam()) { + expiredSteamableIndices.add(index); + continue; + } + + // Update block + zone.updateBlock(x, y, item.getLayer(), item, 0); + } + + // Unindex expired steamables + for(int index : expiredSteamableIndices) { + steamableIndices.remove(index); + } + + // Enqueue blocks at the spouts of all collectors + for(int index : collectorIndices) { + int x = index % zone.getWidth(); + int y = index / zone.getWidth(); + + // Skip if no player is close to this collector + if(zone.getPlayersInRange(x, y, MAX_COLLECTOR_DISTANCE).isEmpty()) { + continue; + } + + // Queue spouts + processQueue.add(new SteamIteration(x + 1, y - 3, 0, 0)); // Top + processQueue.add(new SteamIteration(x + 3, y - 1, 1, 0)); // Right + processQueue.add(new SteamIteration(x + 1, y + 1, 2, 0)); // Bottom + processQueue.add(new SteamIteration(x - 1, y - 1, 3, 0)); // Left + } + + // Travel down the pipeline and power on any machines that are reached by it + while(!processQueue.isEmpty()) { + SteamIteration iteration = processQueue.poll(); + int depth = iteration.getDepth(); + + // Skip if depth limit has been reached + if(depth >= MAX_ITERATIONS) { + continue; + } + + int x = iteration.getX(); + int y = iteration.getY(); + + // Skip if coordinates are out of bounds + if(!zone.areCoordinatesInBounds(x, y)) { + continue; + } + + int index = zone.getBlockIndex(x, y); + + // Skip if block has already been processed + if(processedIndices.contains(index)) { + continue; + } + + processedIndices.add(index); + + // Skip if block is not a pipe + if(getState(x, y) != STATE_PIPE) { + + // ...but activate it first if it uses steam! + if(steamableIndices.contains(index)) { + Item item = zone.getBlock(x, y).getFrontItem(); + zone.updateBlock(x, y, item.getLayer(), item, 1); + } + + continue; + } + + byte direction = iteration.getDirection(); + int nextDepth = depth + 1; + + // Enqueue adjacent blocks for processing + if(direction != 2) processQueue.add(new SteamIteration(x, y - 1, 0, nextDepth)); // Top + if(direction != 3) processQueue.add(new SteamIteration(x + 1, y, 1, nextDepth)); // Right + if(direction != 0) processQueue.add(new SteamIteration(x, y + 1, 2, nextDepth)); // Bottom + if(direction != 1) processQueue.add(new SteamIteration(x - 1, y, 3, nextDepth)); // Left + } + } + + public void indexBlock(int x, int y, Item item) { + int index = zone.getBlockIndex(x, y); + + // Does it use steam? + if(!item.usesSteam()) { + steamableIndices.remove(index); + + // Is it a pipe? + if(!item.hasId("mechanical/pipe")) { + + // Is it a collector and is it on top of a steam vent? + if(!item.hasId("mechanical/collector") || !isCollectorActive(x, y)) { + collectorIndices.remove(index); + setState(index, STATE_EMPTY); + return; + } + + collectorIndices.add(index); + setState(index, STATE_COLLECTOR); + return; + } + + setState(index, STATE_PIPE); + return; + } + + steamableIndices.add(index); + setState(index, STATE_EMPTY); + } + + private boolean isCollectorActive(int x, int y) { + return zone.isChunkLoaded(x + 1, y - 1) && zone.getBlock(x + 1, y - 1).getBaseItem().hasId("base/vent"); + } + + protected void setData(byte[] data) { + // Do nothing if data is null + if(data == null) { + return; + } + + int size = zone.getWidth() * zone.getHeight(); + + // Do nothing if data size is incorrect + if(data.length << 2 != size) { + return; + } + + this.data = data; + + // Index active collectors + for(int i = 0; i < size; i++) { + if(getState(i) == STATE_COLLECTOR) { + collectorIndices.add(i); + } + } + } + + private void setState(int index, byte state) { + int byteOffset = index >> 2; + int bitOffset = (index % 4) << 1; + data[byteOffset] &= ~(0x3 << bitOffset); // Clear bits + data[byteOffset] |= (state & 0x3) << bitOffset; // Set bits + } + + private int getState(int x, int y) { + return getState(zone.getBlockIndex(x, y)); + } + + private int getState(int index) { + return (data[index >> 2] >> ((index % 4) << 1)) & 0x3; + } + + protected byte[] getData() { + return data; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 4fc21e65..5b1eca42 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -76,6 +76,7 @@ public class Zone { private float temperature; private float acidity; private final ChunkManager chunkManager; + private final SteamManager steamManager; private final WeatherManager weatherManager = new WeatherManager(); private final EntityManager entityManager = new EntityManager(this); private final LiquidManager liquidManager = new LiquidManager(this); @@ -98,6 +99,7 @@ protected Zone(String documentId, ZoneConfigFile config, ZoneDataFile data) { this.sunlight = sunlight != null && sunlight.length == width ? sunlight : this.sunlight; this.depths = depths != null && depths.length == 3 ? depths : this.depths; this.chunksExplored = chunksExplored != null && chunksExplored.length == getChunkCount() ? chunksExplored : this.chunksExplored; + steamManager.setData(data.getSteamData()); pendingSunlight.addAll(data.getPendingSunlight()); acidity = biome == Biome.ARCTIC || biome == Biome.SPACE ? 0 : config.getAcidity(); creationDate = config.getCreationDate(); @@ -116,6 +118,7 @@ public Zone(String documentId, String name, Biome biome, int width, int height) sunlight = new int[width]; chunksExplored = new boolean[numChunksWidth * numChunksHeight]; chunkManager = new ChunkManager(this); + steamManager = new SteamManager(this); acidity = biome == Biome.ARCTIC || biome == Biome.SPACE ? 0 : 1; Arrays.fill(surface, height); Arrays.fill(sunlight, height); @@ -131,6 +134,7 @@ public void tick(float deltaTime) { weatherManager.tick(deltaTime); entityManager.tick(deltaTime); liquidManager.tick(deltaTime); + steamManager.tick(deltaTime); // One full cycle = 1200 seconds = 20 minutes time += deltaTime * (1.0F / 1200.0F); @@ -937,6 +941,7 @@ public void updateBlock(int x, int y, Layer layer, Item item, int mod, Player ow removeBlockTimer(x, y); entityManager.trySpawnBlockEntity(x, y); + steamManager.indexBlock(x, y, item); if(item.isWhole() && y < sunlight[x]) { sunlight[x] = y; @@ -1215,10 +1220,12 @@ protected void onChunkLoaded(Chunk chunk) { for(int y = chunkY; y < chunkY + chunk.getHeight(); y++) { // Spawn block-related entities entityManager.trySpawnBlockEntity(x, y); - - // Index liquids Block block = chunk.getBlock(x, y); + // Index steam blocks + steamManager.indexBlock(x, y, block.getFrontItem()); + + // Index liquids if(!block.getLiquidItem().isAir() && block.getLiquidMod() > 0) { liquidManager.indexLiquidBlock(x, y); } @@ -1379,6 +1386,10 @@ public boolean isAreaExplored(int x, int y) { return areCoordinatesInBounds(x, y) && chunksExplored[getChunkIndex(x, y)]; } + protected byte[] getSteamData() { + return steamManager.getData(); + } + public File getDirectory() { return new File("zones", documentId); } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneDataFile.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneDataFile.java index 6eeaf4a6..1e5fc977 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneDataFile.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneDataFile.java @@ -26,16 +26,20 @@ public class ZoneDataFile { @JsonSetter(nulls = Nulls.AS_EMPTY) private boolean[] chunksExplored = {}; + @JsonSetter(nulls = Nulls.AS_EMPTY) + private byte[] steamData = {}; + public ZoneDataFile(Zone zone) { - this(zone.getSurface(), zone.getSunlight(), zone.getDepths(), zone.getPendingSunlight(), zone.getChunksExplored()); + this(zone.getSurface(), zone.getSunlight(), zone.getDepths(), zone.getPendingSunlight(), zone.getChunksExplored(), zone.getSteamData()); } - public ZoneDataFile(int[] surface, int[] sunlight, int[] depths, Collection pendingSunlight, boolean[] chunksExplored) { + public ZoneDataFile(int[] surface, int[] sunlight, int[] depths, Collection pendingSunlight, boolean[] chunksExplored, byte[] steamData) { this.surface = surface; this.sunlight = sunlight; this.depths = depths; this.pendingSunlight = pendingSunlight; this.chunksExplored = chunksExplored; + this.steamData = steamData; } @JsonCreator @@ -60,4 +64,8 @@ public Collection getPendingSunlight() { public boolean[] getChunksExplored() { return chunksExplored; } + + public byte[] getSteamData() { + return steamData; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java index bfcac9d0..38608eef 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java @@ -146,7 +146,7 @@ private ZoneDataFile convertLegacyDataFile(File legacyFile, File outputFile) thr chunksExplored[i] = unpacker.unpackBoolean(); } - ZoneDataFile data = new ZoneDataFile(surface, sunlight, null, pendingSunlight, chunksExplored); + ZoneDataFile data = new ZoneDataFile(surface, sunlight, null, pendingSunlight, chunksExplored, null); Files.write(outputFile.toPath(), ZipUtils.deflateBytes(mapper.writeValueAsBytes(data))); return data; } From e7c48133cc20fc6a3ab4e23365189b49aa103abe Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 20 Feb 2024 04:05:27 +0100 Subject: [PATCH 022/176] Add hatchet functionality --- .../java/brainwine/gameserver/item/Action.java | 1 + .../server/requests/BlockMineRequest.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/Action.java b/gameserver/src/main/java/brainwine/gameserver/item/Action.java index 6c2acf6b..543ecee4 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/Action.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/Action.java @@ -28,6 +28,7 @@ public enum Action { REFILL(new RefillConsumable()), SKILL(new SkillConsumable()), SKILL_RESET(new SkillResetConsumable()), + SMASH, STEALTH(new StealthConsumable()), TELEPORT(new TeleportConsumable()), diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index 349afbf6..0d2fc4cc 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -95,6 +95,23 @@ public void process(Player player) { return; } + // Apply decay if block is being mined with a hatchet + if(item.getMod() == ModType.DECAY && player.getHeldItem().getAction() == Action.SMASH) { + int nextMod = Math.min(4, block.getMod(layer) + 1); + zone.updateBlock(x, y, layer, item, nextMod); + + // Send inventory message for v3 players + if(player.isV3()) { + Item decayItem = item.getDecayInventoryItem(); + + if(!decayItem.isAir()) { + player.sendDelayedMessage(new InventoryMessage(player.getInventory().getClientConfig(decayItem))); + } + } + + return; + } + if(metaBlock != null) { Map metadata = metaBlock.getMetadata(); From c5ff0ba62ec69596ee385d1a09edfb1049097a46 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 20 Feb 2024 04:21:37 +0100 Subject: [PATCH 023/176] Fix an oopsie --- .../gameserver/server/requests/BlockMineRequest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index 0d2fc4cc..dd167a70 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -174,10 +174,10 @@ public void process(Player player) { } } - zone.updateBlock(x, y, layer, 0, 0, player); - player.getStatistics().trackItemMined(item); Item inventoryItem = item.getMod() == ModType.DECAY && block.getMod(layer) > 0 ? item.getDecayInventoryItem() : item.getInventoryItem(); int quantity = 1; + player.getStatistics().trackItemMined(item); + zone.updateBlock(x, y, layer, 0, 0, player); // Apply mining bonus if there is one if(item.hasMiningBonus()) { From 39f75f83221718396c5c8ee554f994d46add0af0 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Fri, 23 Feb 2024 23:46:27 +0100 Subject: [PATCH 024/176] Player appearance rework --- .../gameserver/entity/EntityConfig.java | 5 ++ .../brainwine/gameserver/entity/npc/Npc.java | 6 ++ .../gameserver/entity/player/Appearance.java | 87 +++++++++++++++++++ .../entity/player/AppearanceSlot.java | 61 +++++++++++++ .../entity/player/ClothingSlot.java | 42 --------- .../gameserver/entity/player/ColorSlot.java | 32 ------- .../gameserver/entity/player/Player.java | 55 +++--------- .../entity/player/PlayerConfigFile.java | 15 +--- .../java/brainwine/gameserver/item/Item.java | 12 +++ .../gameserver/item/ItemRegistry.java | 16 ++++ .../requests/ChangeAppearanceRequest.java | 81 ++++++++++------- 11 files changed, 254 insertions(+), 158 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/player/Appearance.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/player/AppearanceSlot.java delete mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/player/ClothingSlot.java delete mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/player/ColorSlot.java diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java b/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java index 0da885c5..31d21bed 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java @@ -30,6 +30,7 @@ public class EntityConfig { private float maxHealth = Entity.DEFAULT_HEALTH; private float baseSpeed = 3; private boolean character; + private boolean human; private boolean named; private Vector2i size = new Vector2i(1, 1); private EntityGroup group = EntityGroup.NONE; @@ -85,6 +86,10 @@ public boolean isCharacter() { return character; } + public boolean isHuman() { + return human; + } + public boolean isNamed() { return named; } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java index 1b931899..db53dba9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java @@ -17,6 +17,7 @@ import brainwine.gameserver.entity.EntityRegistry; import brainwine.gameserver.entity.FacingDirection; import brainwine.gameserver.entity.npc.behavior.SequenceBehavior; +import brainwine.gameserver.entity.player.Appearance; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; @@ -104,6 +105,11 @@ public Npc(Zone zone, EntityConfig config) { this.name = Naming.getRandomEntityName(); } + // Generate random appearance + if(config.isHuman()) { + properties.putAll(Appearance.getRandomAppearance()); + } + this.config = config; this.typeName = config.getName(); this.type = config.getType(); diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Appearance.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/Appearance.java new file mode 100644 index 00000000..34684def --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/Appearance.java @@ -0,0 +1,87 @@ +package brainwine.gameserver.entity.player; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import brainwine.gameserver.GameConfiguration; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.ItemRegistry; +import brainwine.gameserver.util.MapHelper; + +/** + * Utility class for player appearance related stuff. + * Ghosts also have a random appearance, which is why it's here instead of in the player class. + */ +public class Appearance { + + public static Map getRandomAppearance() { + return getRandomAppearance(null); + } + + public static Map getRandomAppearance(Player player) { + Map appearance = new HashMap<>(); + + for(AppearanceSlot slot : AppearanceSlot.values()) { + // Skip if slot cannot be changed by players + if(!slot.isChangeable()) { + continue; + } + + String category = slot.getCategory(); + + // Color handling + if(slot.isColor()) { + List colors = getAvailableColors(slot, player); + + // Change appearance to random color + if(!colors.isEmpty()) { + appearance.put(slot.getId(), colors.get((int)(Math.random() * colors.size()))); + } + + continue; + } + + // Fetch list of items in this slot's category that the player owns + List items = ItemRegistry.getItemsByCategory(category).stream() + .filter(item -> item.isBase() || (player != null && player.getInventory().hasItem(item))) + .collect(Collectors.toList()); + + // Change appearance to random clothing item + if(!items.isEmpty()) { + appearance.put(slot.getId(), items.get((int)(Math.random() * items.size())).getCode()); + } + } + + return appearance; + } + + public static List getAvailableColors(AppearanceSlot slot) { + return getAvailableColors(null); + } + + public static List getAvailableColors(AppearanceSlot slot, Player player) { + List colors = new ArrayList<>(); + + // Return empty list if slot is not valid + if(!slot.isColor()) { + return colors; + } + + Map wardrobe = MapHelper.getMap(GameConfiguration.getBaseConfig(), "wardrobe", Collections.emptyMap()); + String category = slot.getCategory(); + + // Add base colors + colors.addAll(MapHelper.getList(wardrobe, category, Collections.emptyList())); + + // Add bonus colors + if(player != null && player.getInventory().hasItem(ItemRegistry.getItem("accessories/makeup"))) { + colors.addAll(MapHelper.getList(wardrobe, String.format("%s-bonus", category), Collections.emptyList())); + } + + return colors; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/AppearanceSlot.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/AppearanceSlot.java new file mode 100644 index 00000000..43ed761a --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/AppearanceSlot.java @@ -0,0 +1,61 @@ +package brainwine.gameserver.entity.player; + +public enum AppearanceSlot { + + SKIN_COLOR("c*", "skin-color", true), + HAIR_COLOR("h*", "hair-color", true), + HAIR("h", "hair", true), + FACIAL_HAIR("fh", "facialhair", true), + TOPS("t", "tops", true), + BOTTOMS("b", "bottoms", true), + FOOTWEAR("fw", "footwear", true), + HEADGEAR("hg", "headgear", true), + FACIAL_GEAR("fg", "facialgear", true), + FACIAL_GEAR_GLOW("fg*", "facialgear-glow"), + SUIT("u", "suit"), + TOPS_OVERLAY("to", "tops-overlay"), + TOPS_OVERLAY_GLOW("to*", "tops-overlay-glow"), + ARMS_OVERLAY("ao", "arms-overlay"), + LEGS_OVERLAY("lo", "legs-overlay"), + FOOTWEAR_OVERLAY("fo", "footwear-overlay"); + + private final String id; + private final String category; + private final boolean changeable; + + private AppearanceSlot(String id, String category) { + this(id, category, false); + } + + private AppearanceSlot(String id, String category, boolean changeable) { + this.id = id; + this.category = category; + this.changeable = changeable; + } + + public static AppearanceSlot fromId(String id) { + for(AppearanceSlot value : values()) { + if(value.getId().equals(id)) { + return value; + } + } + + return null; + } + + public String getId() { + return id; + } + + public String getCategory() { + return category; + } + + public boolean isChangeable() { + return changeable; + } + + public boolean isColor() { + return id.endsWith("*"); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/ClothingSlot.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/ClothingSlot.java deleted file mode 100644 index f0ab70be..00000000 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/ClothingSlot.java +++ /dev/null @@ -1,42 +0,0 @@ -package brainwine.gameserver.entity.player; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -public enum ClothingSlot { - - HAIR("h"), - FACIAL_HAIR("fh"), - TOPS("t"), - BOTTOMS("b"), - FOOTWEAR("fw"), - HEADGEAR("hg"), - FACIAL_GEAR("fg"), - SUIT("u"), - TOPS_OVERLAY("to"), - ARMS_OVERLAY("ao"), - LEGS_OVERLAY("lo"), - FOOTWEAR_OVERLAY("fo"); - - private final String id; - - private ClothingSlot(String id) { - this.id = id; - } - - @JsonCreator - public static ClothingSlot fromId(String id) { - for(ClothingSlot value : values()) { - if(value.getId().equals(id)) { - return value; - } - } - - return null; - } - - @JsonValue - public String getId() { - return id; - } -} diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/ColorSlot.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/ColorSlot.java deleted file mode 100644 index 8fe4caa7..00000000 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/ColorSlot.java +++ /dev/null @@ -1,32 +0,0 @@ -package brainwine.gameserver.entity.player; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonValue; - -public enum ColorSlot { - - SKIN_COLOR("c*"), - HAIR_COLOR("h*"); - - private final String id; - - private ColorSlot(String id) { - this.id = id; - } - - @JsonCreator - public static ColorSlot fromId(String id) { - for(ColorSlot value : values()) { - if(value.getId().equals(id)) { - return value; - } - } - - return null; - } - - @JsonValue - public String getId() { - return id; - } -} diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java index fc0b0587..64edb0f3 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java @@ -10,7 +10,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -103,8 +102,7 @@ public class Player extends Entity implements CommandExecutor { private Map ignoredHints; private Map skills; private Map> bumpedSkills; - private Map equippedClothing; - private Map equippedColors; + private Map appearance; private final Map settings = new HashMap<>(); private final Set activeChunks = new HashSet<>(); private final Map> dialogs = new HashMap<>(); @@ -147,8 +145,7 @@ protected Player(String documentId, PlayerConfigFile config) { this.ignoredHints = config.getIgnoredHints(); this.skills = config.getSkills(); this.bumpedSkills = config.getBumpedSkills(); - this.equippedClothing = config.getEquippedClothing(); - this.equippedColors = config.getEquippedColors(); + this.appearance = config.getAppearance(); health = getMaxHealth(); inventory.setPlayer(this); statistics.setPlayer(this); @@ -169,8 +166,7 @@ public Player(String documentId, String name, Zone zone) { this.ignoredHints = new HashMap<>(); this.skills = new HashMap<>(); this.bumpedSkills = new HashMap<>(); - this.equippedClothing = new HashMap<>(); - this.equippedColors = new HashMap<>(); + this.appearance = Appearance.getRandomAppearance(); } @JsonCreator @@ -271,7 +267,8 @@ public void setProperties(Map properties, boolean sendMessage) { public Map getStatusConfig() { Map config = super.getStatusConfig(); config.put("id", documentId); - config.putAll(getAppearanceConfig()); + config.putAll(appearance); + config.put("u", inventory.findJetpack().getCode()); return config; } @@ -1061,27 +1058,18 @@ public Set getAchievements() { return Collections.unmodifiableSet(achievements); } - public void setClothing(ClothingSlot slot, Item item) { - if(!item.isClothing()) { - return; - } - - equippedClothing.put(slot, item); - zone.sendMessage(new EntityChangeMessage(id, getAppearanceConfig())); + public void randomizeAppearance() { + appearance.putAll(Appearance.getRandomAppearance(this)); + zone.sendMessage(new EntityChangeMessage(id, appearance)); } - public Map getEquippedClothing() { - return Collections.unmodifiableMap(equippedClothing); + public void updateAppearance(Map appearance) { + this.appearance = appearance; + zone.sendMessage(new EntityChangeMessage(id, appearance)); } - public void setColor(ColorSlot slot, String hex) { - // TODO check if the string is actually a valid hex color - equippedColors.put(slot, hex); - zone.sendMessage(new EntityChangeMessage(id, getAppearanceConfig())); - } - - public Map getEquippedColors() { - return Collections.unmodifiableMap(equippedColors); + public Map getAppearance() { + return Collections.unmodifiableMap(appearance); } public void setSkillLevel(Skill skill, int level) { @@ -1309,21 +1297,6 @@ public boolean isOnline() { return connection != null && connection.isOpen(); } - private Map getAppearanceConfig() { - Map appearance = new HashMap<>(); - - for(Entry entry : equippedClothing.entrySet()) { - appearance.put(entry.getKey().getId(), entry.getValue().getCode()); - } - - for(Entry entry : equippedColors.entrySet()) { - appearance.put(entry.getKey().getId(), entry.getValue()); - } - - appearance.put(ClothingSlot.SUIT.getId(), inventory.findJetpack().getCode()); // Jetpack - return appearance; - } - /** * @return A {@link Map} containing all the data necessary for use in {@link ConfigurationMessage}. */ @@ -1345,7 +1318,7 @@ public Map getClientConfig() { config.put("items_crafted", statistics.getTotalItemsCrafted()); config.put("play_time", (int)(statistics.getPlayTime())); config.put("deaths", statistics.getDeaths()); - config.put("appearance", getAppearanceConfig()); + config.put("appearance", appearance); config.put("settings", settings); return config; } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerConfigFile.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerConfigFile.java index e786466f..03fac5d0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerConfigFile.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerConfigFile.java @@ -39,8 +39,7 @@ public class PlayerConfigFile { private Map ignoredHints = new HashMap<>(); private Map skills = new HashMap<>(); private Map> bumpedSkills = new HashMap<>(); - private Map equippedClothing = new HashMap<>(); - private Map equippedColors = new HashMap<>(); + private Map appearance = new HashMap<>(); public PlayerConfigFile(Player player) { this.name = player.getName(); @@ -63,8 +62,7 @@ public PlayerConfigFile(Player player) { this.ignoredHints = player.getIgnoredHints(); this.skills = player.getSkills(); this.bumpedSkills = player.getBumpedSkills(); - this.equippedClothing = player.getEquippedClothing(); - this.equippedColors = player.getEquippedColors(); + this.appearance = player.getAppearance(); } @JsonCreator @@ -163,12 +161,7 @@ public Map> getBumpedSkills() { } @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) - public Map getEquippedClothing() { - return equippedClothing; - } - - @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) - public Map getEquippedColors() { - return equippedColors; + public Map getAppearance() { + return appearance; } } diff --git a/gameserver/src/main/java/brainwine/gameserver/item/Item.java b/gameserver/src/main/java/brainwine/gameserver/item/Item.java index 6e3a7ec8..605dcc8e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/Item.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/Item.java @@ -30,6 +30,9 @@ public class Item { @JsonProperty("code") private int code; + @JsonProperty("category") + private String category; + @JsonProperty("title") private String title; @@ -237,6 +240,15 @@ public int getCode() { return code; } + public String getCategory() { + if(category != null) { + return category; + } + + int index = id.indexOf('/'); + return index > 1 ? id.substring(0, index) : null; + } + public String getTitle() { return title; } diff --git a/gameserver/src/main/java/brainwine/gameserver/item/ItemRegistry.java b/gameserver/src/main/java/brainwine/gameserver/item/ItemRegistry.java index 4b82d2c5..db35ed79 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/ItemRegistry.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/ItemRegistry.java @@ -2,9 +2,11 @@ import static brainwine.shared.LogMarkers.SERVER_MARKER; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.logging.log4j.LogManager; @@ -15,6 +17,7 @@ public class ItemRegistry { private static final Logger logger = LogManager.getLogger(); private static final Map items = new HashMap<>(); private static final Map itemsByCode = new HashMap<>(); + private static final Map> itemsByCategory = new HashMap<>(); // TODO maybe just move the registry stuff here public static void clear() { @@ -36,6 +39,15 @@ public static boolean registerItem(Item item) { return false; } + String category = item.getCategory(); + List categorizedItems = itemsByCategory.get(category); + + if(categorizedItems == null) { + categorizedItems = new ArrayList<>(); + itemsByCategory.put(category, categorizedItems); + } + + categorizedItems.add(item); items.put(id, item); itemsByCode.put(code, item); return true; @@ -52,4 +64,8 @@ public static Item getItem(int code) { public static Collection getItems() { return Collections.unmodifiableCollection(items.values()); } + + public static List getItemsByCategory(String category) { + return Collections.unmodifiableList(itemsByCategory.getOrDefault(category, Collections.emptyList())); + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/ChangeAppearanceRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/ChangeAppearanceRequest.java index beb7de26..6c903af8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/ChangeAppearanceRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/ChangeAppearanceRequest.java @@ -5,29 +5,28 @@ import brainwine.gameserver.annotations.RequestInfo; import brainwine.gameserver.dialog.DialogHelper; -import brainwine.gameserver.entity.player.ClothingSlot; -import brainwine.gameserver.entity.player.ColorSlot; +import brainwine.gameserver.entity.player.Appearance; +import brainwine.gameserver.entity.player.AppearanceSlot; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.messages.EntityChangeMessage; +import brainwine.gameserver.util.MapHelper; -/** - * TODO we should actually check if the sent value is even compatible with the slot. - * We wouldn't want to allow players to equip pants for hats! - */ @RequestInfo(id = 22) public class ChangeAppearanceRequest extends PlayerRequest { - public Map data; + public Map appearance; @Override public void process(Player player) { - if(data.containsKey("meta")) { - String meta = "" + data.get("meta"); + // Handle special cases + if(appearance.containsKey("meta")) { + String meta = MapHelper.getString(appearance, "meta", ""); if(meta.equals("randomize")) { - player.notify("Sorry, you can't randomize your appearance yet."); + player.randomizeAppearance(); } else { player.showDialog(DialogHelper.getWardrobeDialog(meta)); } @@ -35,36 +34,54 @@ public void process(Player player) { return; } - for(Entry entry : data.entrySet()) { - String key = entry.getKey(); + // Validate appearance data + for(Entry entry : appearance.entrySet()) { + AppearanceSlot slot = AppearanceSlot.fromId(entry.getKey()); Object value = entry.getValue(); - if(value instanceof Integer) { - ClothingSlot slot = ClothingSlot.fromId(key); - - if(slot == null) { - continue; - } - - Item item = ItemRegistry.getItem((int)value); - - if(!item.isBase() && !player.getInventory().hasItem(item)) { - player.notify("Sorry, but you do not own this."); + // Fail if slot is not valid + if(slot == null || !slot.isChangeable()) { + fail(player); + return; + } + + // Handle color data + if(slot.isColor()) { + // Fail if color value is not a string + if(!(value instanceof String)) { + fail(player); return; } - player.setClothing(slot, item); - } else if(value instanceof String) { - // TODO check if player owns color - ColorSlot slot = ColorSlot.fromId(key); - String color = (String)value; - - if(slot == null) { - continue; + // Fail if player doesn't own color + if(!Appearance.getAvailableColors(slot, player).contains((String)value)) { + fail(player); + return; } - player.setColor(slot, color); + continue; + } + + // Fail if item value is not an integer (item code) + if(!(value instanceof Integer)) { + fail(player); + return; + } + + Item item = ItemRegistry.getItem((int)value); + + // Do nothing if item isn't valid clothing or player doesn't own it + if(!item.isClothing() || !slot.getCategory().equals(item.getCategory()) || (!item.isBase() && !player.getInventory().hasItem(item))) { + fail(player); + return; } } + + // Update player appearance + player.updateAppearance(appearance); + } + + private void fail(Player player) { + player.sendMessage(new EntityChangeMessage(player.getId(), player.getAppearance())); } } From 34ca131f610d8b963f45d067ae1a83541cb7eff8 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 24 Feb 2024 00:00:32 +0100 Subject: [PATCH 025/176] Fix zone status compatibility with older game versions --- .../gameserver/entity/player/Player.java | 10 +++++-- .../server/messages/ZoneStatusMessage.java | 6 ++-- .../java/brainwine/gameserver/zone/Zone.java | 28 +++++++++++-------- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java index 64edb0f3..6fe19eab 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java @@ -66,6 +66,7 @@ import brainwine.gameserver.server.pipeline.Connection; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.MathUtils; +import brainwine.gameserver.util.VersionUtils; import brainwine.gameserver.zone.Chunk; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; @@ -320,8 +321,7 @@ public void onZoneChanged() { } sendMessage(new ConfigurationMessage(id, getClientConfig(), GameConfiguration.getClientConfig(this), zone.getClientConfig(this))); - sendMessage(new ZoneStatusMessage(zone.getStatusConfig())); - sendMessage(new ZoneStatusMessage(zone.getStatusConfig())); + sendMessage(new ZoneStatusMessage(zone.getStatusConfig(this))); sendMessage(new PlayerPositionMessage((int)x, (int)y)); sendMessage(new HealthMessage(health)); @@ -525,8 +525,12 @@ public String getClientVersion() { return clientVersion; } + public boolean hasClientVersion(String version) { + return clientVersion != null && VersionUtils.isGreaterOrEqualTo(clientVersion, version); + } + public boolean isV3() { - return clientVersion != null && clientVersion.startsWith("3"); + return hasClientVersion("3.0.0"); } /** diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneStatusMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneStatusMessage.java index 635ba757..09c3b3b8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneStatusMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneStatusMessage.java @@ -1,16 +1,14 @@ package brainwine.gameserver.server.messages; -import java.util.Map; - import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; @MessageInfo(id = 17) public class ZoneStatusMessage extends Message { - public Map status; + public Object status; - public ZoneStatusMessage(Map status) { + public ZoneStatusMessage(Object status) { this.status = status; } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 5b1eca42..7f44679f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -143,9 +143,13 @@ public void tick(float deltaTime) { time -= 1.0F; } + // Send zone status update if(!getPlayers().isEmpty()) { if(now >= lastStatusUpdate + 4000) { - sendMessage(new ZoneStatusMessage(getStatusConfig())); + for(Player player : getPlayers()) { + sendMessage(new ZoneStatusMessage(getStatusConfig(player))); + } + lastStatusUpdate = now; } } @@ -1522,16 +1526,16 @@ public Map getClientConfig(Player player) { /** * @return A {@link Map} containing all the data necessary for use in {@link ZoneStatusMessage}. */ - public Map getStatusConfig() { - Map config = new HashMap<>(); - config.put("w", new int[] { - (int)(time * 10000), - (int)(temperature * 10000), - (int)(weatherManager.getPrecipitation() * 10000), - (int)(weatherManager.getPrecipitation() * 10000), - (int)(weatherManager.getPrecipitation() * 10000), - (int)(acidity * 10000) - }); - return config; + public Object getStatusConfig(Player player) { + int[] status = { + (int)(time * 10000), + (int)(temperature * 10000), + (int)(weatherManager.getPrecipitation() * 10000), + (int)(weatherManager.getPrecipitation() * 10000), + (int)(weatherManager.getPrecipitation() * 10000), + (int)(acidity * 10000) + }; + + return player.hasClientVersion("2.1.0") ? MapHelper.map("w", status) : status; } } From b737d46b1d53260c86fd4d1d20aa8bb9745d965e Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 24 Feb 2024 00:05:08 +0100 Subject: [PATCH 026/176] Make `/give all` not exclude clothing items --- .../java/brainwine/gameserver/command/commands/GiveCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/GiveCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/commands/GiveCommand.java index 6102bc81..56fb90b2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/GiveCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/commands/GiveCommand.java @@ -35,7 +35,7 @@ public void execute(CommandExecutor executor, String[] args) { title = "of every item"; for(Item item : ItemRegistry.getItems()) { - if(!item.isClothing() && !item.isAir()) { + if(!item.isAir()) { items.add(item); } } From 5dbc7555deb89c5135b73107c9b04247b8a41ba3 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 24 Feb 2024 00:30:47 +0100 Subject: [PATCH 027/176] Fix appearance update oopsie --- .../main/java/brainwine/gameserver/entity/player/Player.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java index 6fe19eab..cf03310f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java @@ -1068,7 +1068,7 @@ public void randomizeAppearance() { } public void updateAppearance(Map appearance) { - this.appearance = appearance; + this.appearance.putAll(appearance); zone.sendMessage(new EntityChangeMessage(id, appearance)); } From 7a06129e8bad1e7b254fab304a64959c7fea05f2 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 24 Feb 2024 00:34:53 +0100 Subject: [PATCH 028/176] Prepack `EntityItemUseMessage` --- .../gameserver/entity/player/Player.java | 6 +-- .../server/messages/EntityItemUseMessage.java | 28 +++++++----- .../server/models/EntityItemUseData.java | 43 +++++++++++++++++++ 3 files changed, 62 insertions(+), 15 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/server/models/EntityItemUseData.java diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java index cf03310f..865f8cfc 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java @@ -338,11 +338,7 @@ public void onZoneChanged() { Collection peers = zone.getPlayers(); sendMessage(new EntityStatusMessage(peers, EntityStatus.ENTERING)); sendMessage(new EntityPositionMessage(peers)); - - // TODO prepack this as well - for(Player peer : peers) { - sendMessage(new EntityItemUseMessage(peer.getId(), 0, peer.getHeldItem(), 0)); - } + sendMessage(new EntityItemUseMessage(peers)); // Send achievement data for(Achievement achievement : AchievementManager.getAchievements()) { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityItemUseMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityItemUseMessage.java index aec81da2..35381989 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityItemUseMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityItemUseMessage.java @@ -1,21 +1,29 @@ package brainwine.gameserver.server.messages; +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; + import brainwine.gameserver.annotations.MessageInfo; +import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.models.EntityItemUseData; -@MessageInfo(id = 10, collection = true) +@MessageInfo(id = 10, prepacked = true) public class EntityItemUseMessage extends Message { - public int entityId; - public int type; - public Item item; - public int status; + public Collection data; + + public EntityItemUseMessage(Collection players) { + this.data = players.stream().map(EntityItemUseData::new).collect(Collectors.toList()); + } + + public EntityItemUseMessage(Player player) { + this.data = Arrays.asList(new EntityItemUseData(player)); + } - public EntityItemUseMessage(int entityId, int type, Item item, int status) { - this.entityId = entityId; - this.type = type; - this.item = item; - this.status = status; + public EntityItemUseMessage(int id, int type, Item item, int status) { + this.data = Arrays.asList(new EntityItemUseData(id, type, item, status)); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/models/EntityItemUseData.java b/gameserver/src/main/java/brainwine/gameserver/server/models/EntityItemUseData.java new file mode 100644 index 00000000..9c663da3 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/server/models/EntityItemUseData.java @@ -0,0 +1,43 @@ +package brainwine.gameserver.server.models; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; + +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; + +@JsonFormat(shape = Shape.ARRAY) +public class EntityItemUseData { + + private final int id; + private final int type; + private final Item item; + private final int status; + + public EntityItemUseData(Player player) { + this(player.getId(), 0, player.getHeldItem(), 0); + } + + public EntityItemUseData(int id, int type, Item item, int status) { + this.id = id; + this.type = type; + this.item = item; + this.status = status; + } + + public int getId() { + return id; + } + + public int getType() { + return type; + } + + public Item getItem() { + return item; + } + + public int getStatus() { + return status; + } +} From 1466ea0176a4e473afb90388a33d0d95075ade7a Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 24 Feb 2024 00:46:23 +0100 Subject: [PATCH 029/176] Fix player name inconsistency on v2 clients --- api/src/main/java/brainwine/api/DataFetcher.java | 1 + api/src/main/java/brainwine/api/DefaultDataFetcher.java | 5 +++++ api/src/main/java/brainwine/api/GatewayService.java | 2 +- src/main/java/brainwine/DirectDataFetcher.java | 7 +++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/brainwine/api/DataFetcher.java b/api/src/main/java/brainwine/api/DataFetcher.java index a72d6ad9..1509e9a7 100644 --- a/api/src/main/java/brainwine/api/DataFetcher.java +++ b/api/src/main/java/brainwine/api/DataFetcher.java @@ -9,6 +9,7 @@ public interface DataFetcher { public boolean isPlayerNameTaken(String name); public String registerPlayer(String name); public String login(String name, String password); + public String fetchPlayerName(String name); public boolean verifyAuthToken(String name, String token); public boolean verifyApiToken(String apiToken); public Collection fetchZoneInfo(); diff --git a/api/src/main/java/brainwine/api/DefaultDataFetcher.java b/api/src/main/java/brainwine/api/DefaultDataFetcher.java index 9c6b42c3..8a5b7242 100644 --- a/api/src/main/java/brainwine/api/DefaultDataFetcher.java +++ b/api/src/main/java/brainwine/api/DefaultDataFetcher.java @@ -23,6 +23,11 @@ public String login(String name, String password) { throw exception; } + @Override + public String fetchPlayerName(String name) { + throw exception; + } + @Override public boolean verifyAuthToken(String name, String token) { throw exception; diff --git a/api/src/main/java/brainwine/api/GatewayService.java b/api/src/main/java/brainwine/api/GatewayService.java index 2ff42eb7..c2927ea8 100644 --- a/api/src/main/java/brainwine/api/GatewayService.java +++ b/api/src/main/java/brainwine/api/GatewayService.java @@ -111,7 +111,7 @@ private void handlePlayerLogin(Context ctx) { return; } - ctx.json(new ServerConnectInfo(api.getGameServerHost(), name, token)); + ctx.json(new ServerConnectInfo(api.getGameServerHost(), dataFetcher.fetchPlayerName(name), token)); } /** diff --git a/src/main/java/brainwine/DirectDataFetcher.java b/src/main/java/brainwine/DirectDataFetcher.java index 76a8bdb7..ad6fdaa8 100644 --- a/src/main/java/brainwine/DirectDataFetcher.java +++ b/src/main/java/brainwine/DirectDataFetcher.java @@ -6,6 +6,7 @@ import brainwine.api.DataFetcher; import brainwine.api.models.ZoneInfo; +import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.entity.player.PlayerManager; import brainwine.gameserver.zone.Zone; import brainwine.gameserver.zone.ZoneManager; @@ -34,6 +35,12 @@ public String registerPlayer(String name) { public String login(String name, String password) { return playerManager.login(name, password); } + + @Override + public String fetchPlayerName(String name) { + Player player = playerManager.getPlayer(name); + return player == null ? null : player.getName(); + } @Override public boolean verifyAuthToken(String name, String token) { From 882396013ea35b626fba8641b75f1f6d97c8fe6f Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 24 Feb 2024 02:31:03 +0100 Subject: [PATCH 030/176] Temporary fix for system notifications playing karma warning sound on v2 --- .../java/brainwine/gameserver/entity/player/Player.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java index 865f8cfc..f24e0bee 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java @@ -213,11 +213,8 @@ public void die(EntityAttack cause) { @Override public void notify(Object message, NotificationType type) { - if(type == NotificationType.SYSTEM && isV3()) { - sendMessage(new NotificationMessage(message, NotificationType.PEER_ACCOMPLISHMENT)); - } else { - sendMessage(new NotificationMessage(message, type)); - } + // TODO type SYSTEM (2) apparently plays the karma warning sound on v2 clients, so I guess we'll be mapping all of them to PEER_ACCOMPLISHMENT (11). + sendMessage(new NotificationMessage(message, type == NotificationType.SYSTEM ? NotificationType.PEER_ACCOMPLISHMENT : type)); } @Override From c444371612c56230cb85c989fbbe414d5a237354 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 24 Feb 2024 03:27:56 +0100 Subject: [PATCH 031/176] Send killer entity ID in player death status message --- .../gameserver/entity/player/Player.java | 17 +++++++++++++++-- .../server/messages/EntityStatusMessage.java | 4 ++++ .../server/models/EntityStatusData.java | 6 +++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java index f24e0bee..72e44ae9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java @@ -206,9 +206,22 @@ public void tick(float deltaTime) { @Override public void die(EntityAttack cause) { + Entity killer = cause == null ? null : cause.getAttacker(); + String serverMessage = String.format("%s died.", name); + Map details = new HashMap<>(); + + if(killer != null) { + details.put("<", killer.getId()); + + if(killer.isPlayer()) { + // TODO track kill for killer achievement in pvp zones + serverMessage = String.format("%s killed %s.", killer.getName(), name); + } + } + + sendMessageToPeers(new EntityStatusMessage(this, EntityStatus.DEAD, details)); + GameServer.getInstance().notify(serverMessage, NotificationType.CHAT); statistics.trackDeath(); - sendMessageToPeers(new EntityStatusMessage(this, EntityStatus.DEAD)); // TODO killer id - GameServer.getInstance().notify(String.format("%s died", name), NotificationType.CHAT); } @Override diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityStatusMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityStatusMessage.java index accc0d7b..cf2c420c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityStatusMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityStatusMessage.java @@ -28,6 +28,10 @@ public EntityStatusMessage(Entity entity, EntityStatus status) { this(Arrays.asList(new EntityStatusData(entity, status))); } + public EntityStatusMessage(Entity entity, EntityStatus status, Map details) { + this(Arrays.asList(new EntityStatusData(entity, status, details))); + } + public EntityStatusMessage(int id, int type, String name, EntityStatus status, Map details) { this(Arrays.asList(new EntityStatusData(id, type, name, status, details))); } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/models/EntityStatusData.java b/gameserver/src/main/java/brainwine/gameserver/server/models/EntityStatusData.java index e7539768..ccc9f341 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/models/EntityStatusData.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/models/EntityStatusData.java @@ -18,7 +18,11 @@ public class EntityStatusData { private final Map details; public EntityStatusData(Entity entity, EntityStatus status) { - this(entity.getId(), entity.getType(), entity.getName(), status, entity.getStatusConfig()); + this(entity, status, entity.getStatusConfig()); + } + + public EntityStatusData(Entity entity, EntityStatus status, Map details) { + this(entity.getId(), entity.getType(), entity.getName(), status, details); } public EntityStatusData(int id, int type, String name, EntityStatus status, Map details) { From 455e63f9277754a023f2d819eca2ec1c7b4122e8 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 24 Feb 2024 20:06:58 +0100 Subject: [PATCH 032/176] Swap biome & size parameters in `/genzone` command --- .../command/commands/GenerateZoneCommand.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/GenerateZoneCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/commands/GenerateZoneCommand.java index 6c316aab..53985f44 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/GenerateZoneCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/commands/GenerateZoneCommand.java @@ -18,20 +18,20 @@ public class GenerateZoneCommand extends Command { @Override public void execute(CommandExecutor executor, String[] args) { - Biome biome = Biome.getRandomBiome(); + Biome biome = args.length > 0 ? Biome.fromName(args[0]) : Biome.getRandomBiome(); int width = biome == Biome.DEEP ? 1200 : 2000; int height = biome == Biome.DEEP ? 1000 : 600; int seed = (int)(Math.random() * Integer.MAX_VALUE); - if(args.length > 0 && args.length < 2) { + if(args.length > 1 && args.length < 3) { executor.notify(String.format("Usage: %s", getUsage(executor)), SYSTEM); return; } - if(args.length >= 2) { + if(args.length >= 3) { try { - width = Integer.parseInt(args[0]); - height = Integer.parseInt(args[1]); + width = Integer.parseInt(args[1]); + height = Integer.parseInt(args[2]); } catch(NumberFormatException e) { executor.notify("Zone width and height must be valid numbers.", SYSTEM); return; @@ -47,10 +47,6 @@ public void execute(CommandExecutor executor, String[] args) { } } - if(args.length >= 3) { - biome = Biome.fromName(args[2]); - } - ZoneGenerator generator = null; if(args.length >= 4) { @@ -106,7 +102,7 @@ public String getDescription() { @Override public String getUsage(CommandExecutor executor) { - return "/genzone [ ] [biome] [generator] [seed]"; + return "/genzone [biome] [ ] [generator] [seed]"; } @Override From e330b13a74282eb8141e0b459eefb15e19669b3f Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 24 Feb 2024 21:15:32 +0100 Subject: [PATCH 033/176] Prevent explosions from destroying unlooted containers --- .../main/java/brainwine/gameserver/zone/Zone.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 7f44679f..d0be509c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -402,6 +402,18 @@ public void explode(int x, int y, float radius, Entity cause, boolean destructiv break; } + // Metadata check + MetaBlock metaBlock = getMetaBlock(positionX, positionY); + + if(metaBlock != null) { + // Do not destroy block if it is a container with loot + if(frontItem.hasUse(ItemUseType.CONTAINER) && metaBlock.hasProperty("$")) { + continue; + } + + // TODO dungeon switch check + } + affectedBlocks.add(position); processed.add(index); } From 94cb2e16f852c2cf83a9fb9355c131a78478d243 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 24 Feb 2024 21:28:47 +0100 Subject: [PATCH 034/176] Don't track explosion triggerer in burst interaction --- .../gameserver/item/interactions/BurstInteraction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/BurstInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/BurstInteraction.java index ecd1b71a..a9b8573c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/BurstInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/BurstInteraction.java @@ -57,7 +57,7 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i boolean destructive = MapHelper.getBoolean(configMap, "destructive"); // Create explosion and destroy block - zone.explode(x, y, range, player, destructive, damage, damageType, effect); + zone.explode(x, y, range, null, destructive, damage, damageType, effect); zone.updateBlock(x, y, layer, 0); } } From bfeb93be809f1b74988d0d6615f1b5c8c01e5c73 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 24 Feb 2024 22:30:15 +0100 Subject: [PATCH 035/176] Biome survival skill requirement check --- .../server/requests/ZoneChangeRequest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneChangeRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneChangeRequest.java index cbf048e8..c87919f5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneChangeRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneChangeRequest.java @@ -1,9 +1,13 @@ package brainwine.gameserver.server.requests; +import brainwine.gameserver.GameConfiguration; import brainwine.gameserver.GameServer; import brainwine.gameserver.annotations.RequestInfo; import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.entity.player.Skill; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.util.MapHelper; +import brainwine.gameserver.zone.Biome; import brainwine.gameserver.zone.Zone; @RequestInfo(id = 24) @@ -23,6 +27,17 @@ public void process(Player player) { return; } + // Check survival requirement unless player has god mode enabled + if(!player.isGodMode()) { + Biome biome = zone.getBiome(); + int survival = MapHelper.getInt(GameConfiguration.getBaseConfig(), String.format("biomes.%s.survival_requirement", biome.getId())); + + if(player.getTotalSkillLevel(Skill.SURVIVAL) < survival) { + player.notify(String.format("Your survival skill needs to be at least level %s to enter %s worlds.", survival, biome.getId())); + return; + } + } + player.changeZone(zone); } } From 03e96be0a4b3455c45403df5076a78d00b828a3d Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 25 Feb 2024 03:25:31 +0100 Subject: [PATCH 036/176] Add skeleton burying & undertaker achievement type --- .../gameserver/achievements/Achievement.java | 1 + .../achievements/UndertakerAchievement.java | 19 +++++++ .../entity/player/PlayerStatistics.java | 16 ++++++ .../server/requests/BlockPlaceRequest.java | 51 +++++++++++++++++++ .../gameserver/zone/EntityManager.java | 16 ++++++ .../java/brainwine/gameserver/zone/Zone.java | 16 ++++++ 6 files changed, 119 insertions(+) create mode 100644 gameserver/src/main/java/brainwine/gameserver/achievements/UndertakerAchievement.java diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/Achievement.java b/gameserver/src/main/java/brainwine/gameserver/achievements/Achievement.java index c74fb030..0e9e6e13 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/Achievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievements/Achievement.java @@ -28,6 +28,7 @@ @Type(name = "ScavengingAchievement", value = ScavengingAchievement.class), @Type(name = "DiscoveryAchievement", value = DiscoveryAchievement.class), @Type(name = "SpawnerStoppageAchievement", value = SpawnerStoppageAchievement.class), + @Type(name = "UndertakerAchievement", value = UndertakerAchievement.class), @Type(name = "Journeyman", value = JourneymanAchievement.class) }) @JsonSerialize(using = AchievementSerializer.class) diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/UndertakerAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievements/UndertakerAchievement.java new file mode 100644 index 00000000..591965d1 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/achievements/UndertakerAchievement.java @@ -0,0 +1,19 @@ +package brainwine.gameserver.achievements; + +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; + +import brainwine.gameserver.entity.player.Player; + +public class UndertakerAchievement extends Achievement { + + @JsonCreator + public UndertakerAchievement(@JacksonInject("title") String title) { + super(title); + } + + @Override + public int getProgress(Player player) { + return player.getStatistics().getUndertakings(); + } +} \ No newline at end of file diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java index f028ad85..22dc3b69 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java @@ -21,6 +21,7 @@ import brainwine.gameserver.achievements.ScavengingAchievement; import brainwine.gameserver.achievements.SidekickAchievement; import brainwine.gameserver.achievements.SpawnerStoppageAchievement; +import brainwine.gameserver.achievements.UndertakerAchievement; import brainwine.gameserver.entity.EntityConfig; import brainwine.gameserver.item.Item; @@ -39,6 +40,7 @@ public class PlayerStatistics { private int containersLooted; private int dungeonsRaided; private int mawsPlugged; + private int undertakings; private int deaths; @JsonIgnore @@ -309,6 +311,20 @@ public int getMawsPlugged() { return mawsPlugged; } + public void trackUndertaking() { + undertakings++; + player.addExperience(25); + player.updateAchievementProgress(UndertakerAchievement.class); + } + + public void setUndertakings(int undertakings) { + this.undertakings = undertakings; + } + + public int getUndertakings() { + return undertakings; + } + public void trackDeath() { deaths++; } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java index 8c6688e9..d024ee2c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java @@ -10,6 +10,7 @@ import brainwine.gameserver.entity.player.Skill; import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.ItemGroup; import brainwine.gameserver.item.Layer; import brainwine.gameserver.item.ModType; import brainwine.gameserver.server.PlayerRequest; @@ -116,6 +117,56 @@ public void process(Player player) { if(item.hasCustomPlace()) { processCustomPlace(zone, player); } + + // Process burial if item placed is a gravestone + if(item.getGroup() == ItemGroup.GRAVESTONE) { + processBurial(zone, player); + } + } + + private void processBurial(Zone zone, Player player) { + // Check bounds + if(x <= 0 || x + 2 > zone.getWidth() || y + 2 > zone.getHeight()) { + return; + } + + // Do nothing if there is no skeleton underneath the gravestone + if(!zone.getBlock(x, y + 1).getFrontItem().hasId("rubble/skeleton")) { + return; + } + + // Do nothing if the skeleton is obstructed + if(zone.isBlockOccupied(x + 1, y + 1, Layer.FRONT)) { + return; + } + + // Do nothing if the skeleton isn't underground + if(!zone.isUnderground(x, y + 1) || !zone.isUnderground(x + 1, y + 1)) { + return; + } + + // Do nothing if the gravestone isn't above ground + if(zone.isUnderground(x, y) || zone.isUnderground(x + 1, y)) { + return; + } + + // Do nothing if the skeleton isn't surrounded by earth + if(!zone.isBlockEarthy(x - 1, y + 1) || !zone.isBlockEarthy(x + 2, y + 1) || !zone.isBlockEarthy(x, y + 2) || !zone.isBlockEarthy(x + 1, y + 2)) { + return; + } + + // Everything checks out -- fill the grave! + zone.updateBlock(x, y + 1, Layer.FRONT, "ground/earth"); + zone.updateBlock(x + 1, y + 1, Layer.FRONT, "ground/earth"); + zone.spawnEffect(x + 1.0F, y + 0.5F, "expiate", 20); + zone.spawnEffect(x + 1.0F, y + 0.5F, "sparkle up", 20); + + // ~33% chance to spawn a ghost + if(Math.random() < 0.334) { + zone.spawnEntity("ghost", x + 1, y); + } + + player.getStatistics().trackUndertaking(); } private void createBlockTimer(Zone zone, Player player) { diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java index 9e7a0999..cfddcec4 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java @@ -260,6 +260,22 @@ public void spawnPersistentNpcs(Collection data) { } } + public Npc spawnEntity(String type, int x, int y) { + return spawnEntity(type, x, y, false); + } + + public Npc spawnEntity(String type, int x, int y, boolean effect) { + EntityConfig config = EntityRegistry.getEntityConfig(type); + + if(config == null) { + return null; + } + + Npc entity = new Npc(zone, config); + spawnEntity(entity, x, y, effect); + return entity; + } + public void spawnEntity(Entity entity, int x, int y) { spawnEntity(entity, x, y, false); } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index d0be509c..7d559f93 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -477,6 +477,10 @@ public void explodeLiquid(int x, int y, int range, Item liquid) { } } + public boolean isBlockEarthy(int x, int y) { + return areCoordinatesInBounds(x, y) && getBlock(x, y).getFrontItem().isEarthy(); + } + public boolean isBlockNatural(int x, int y) { return areCoordinatesInBounds(x, y) && getBlock(x, y).isNatural(); } @@ -1140,6 +1144,14 @@ public void spawnPersistentNpcs(Collection data) { entityManager.spawnPersistentNpcs(data); } + public Npc spawnEntity(String type, int x, int y) { + return entityManager.spawnEntity(type, x, y); + } + + public Npc spawnEntity(String type, int x, int y, boolean effect) { + return entityManager.spawnEntity(type, x, y, effect); + } + public void spawnEntity(Entity entity, int x, int y) { entityManager.spawnEntity(entity, x, y); } @@ -1305,6 +1317,10 @@ public WeatherManager getWeatherManager() { return weatherManager; } + public boolean isUnderground(int x, int y) { + return areCoordinatesInBounds(x, y) && y >= surface[x]; + } + public void setSurface(int x, int surface) { if(areCoordinatesInBounds(x, surface)) { this.surface[x] = surface; From a9ba16815fb04b8fed1a32b5fe7fc87be4ad279b Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 25 Feb 2024 19:15:10 +0100 Subject: [PATCH 037/176] Space biomes should have a higher surface level --- .../gameserver/zone/gen/tasks/TerrainGeneratorTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/TerrainGeneratorTask.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/TerrainGeneratorTask.java index 21e4dc27..281dc861 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/TerrainGeneratorTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/TerrainGeneratorTask.java @@ -33,7 +33,7 @@ public TerrainGeneratorTask(GeneratorConfig config) { public void generate(GeneratorContext ctx) { int width = ctx.getWidth(); int height = ctx.getHeight(); - int surfaceLevel = height < 600 ? height / 3 : 200; + int surfaceLevel = type == TerrainType.ASTEROIDS ? (height < 600 ? height / 6 : 100) : (height < 600 ? height / 3 : 200); int lowestSurfaceLevel = 0; // Determine surface first, then start placing blocks. From e9a046c8ed0085e4a2890ebf3d14950d4ad8511f Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 25 Feb 2024 23:50:30 +0100 Subject: [PATCH 038/176] Add creature trapping & implement trapper achievements --- .../gameserver/achievements/Achievement.java | 1 + .../achievements/TrappingAchievement.java | 19 +++++++ .../gameserver/entity/EntityConfig.java | 14 +++++ .../entity/player/PlayerStatistics.java | 26 +++++++++ .../gameserver/item/ItemUseType.java | 1 + .../server/requests/BlockPlaceRequest.java | 53 ++++++++++++++++++- .../java/brainwine/gameserver/zone/Zone.java | 4 ++ 7 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/achievements/TrappingAchievement.java diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/Achievement.java b/gameserver/src/main/java/brainwine/gameserver/achievements/Achievement.java index 0e9e6e13..00fbfb21 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/Achievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievements/Achievement.java @@ -29,6 +29,7 @@ @Type(name = "DiscoveryAchievement", value = DiscoveryAchievement.class), @Type(name = "SpawnerStoppageAchievement", value = SpawnerStoppageAchievement.class), @Type(name = "UndertakerAchievement", value = UndertakerAchievement.class), + @Type(name = "TrappingAchievement", value = TrappingAchievement.class), @Type(name = "Journeyman", value = JourneymanAchievement.class) }) @JsonSerialize(using = AchievementSerializer.class) diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/TrappingAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievements/TrappingAchievement.java new file mode 100644 index 00000000..72cf2b2b --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/achievements/TrappingAchievement.java @@ -0,0 +1,19 @@ +package brainwine.gameserver.achievements; + +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; + +import brainwine.gameserver.entity.player.Player; + +public class TrappingAchievement extends Achievement { + + @JsonCreator + public TrappingAchievement(@JacksonInject("title") String title) { + super(title); + } + + @Override + public int getProgress(Player player) { + return player.getStatistics().getTotalTrappings(); + } +} \ No newline at end of file diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java b/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java index 31d21bed..fdb43f88 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java @@ -32,6 +32,8 @@ public class EntityConfig { private boolean character; private boolean human; private boolean named; + private boolean trappable; + private Item trappablePetItem; private Vector2i size = new Vector2i(1, 1); private EntityGroup group = EntityGroup.NONE; private WeightedMap loot = new WeightedMap<>(); @@ -94,6 +96,18 @@ public boolean isNamed() { return named; } + public boolean isTrappable() { + return trappable; + } + + public boolean hasTrappablePetItem() { + return trappablePetItem != null && !trappablePetItem.isAir(); + } + + public Item getTrappablePetItem() { + return trappablePetItem; + } + @JsonSetter(nulls = Nulls.SKIP) private void setSize(Vector2i size) { this.size = size; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java index 22dc3b69..d628a5e4 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java @@ -21,6 +21,7 @@ import brainwine.gameserver.achievements.ScavengingAchievement; import brainwine.gameserver.achievements.SidekickAchievement; import brainwine.gameserver.achievements.SpawnerStoppageAchievement; +import brainwine.gameserver.achievements.TrappingAchievement; import brainwine.gameserver.achievements.UndertakerAchievement; import brainwine.gameserver.entity.EntityConfig; import brainwine.gameserver.item.Item; @@ -34,6 +35,7 @@ public class PlayerStatistics { private Map discoveries = new HashMap<>(); private Map kills = new HashMap<>(); private Map assists = new HashMap<>(); + private Map trappings = new HashMap<>(); private float playTime; private int itemsPlaced; private int areasExplored; @@ -224,6 +226,30 @@ public Map getAssists() { return Collections.unmodifiableMap(assists); } + public void trackTrapping(EntityConfig entity) { + trappings.put(entity, getTrappings(entity) + 1); + player.addExperience(5); + player.updateAchievementProgress(TrappingAchievement.class); + } + + public void setTrappings(Map trappings) { + this.trappings = trappings; + } + + public int getTotalTrappings() { + return trappings.values().stream() + .reduce(Integer::sum) + .orElse(0); + } + + public int getTrappings(EntityConfig entity) { + return trappings.getOrDefault(entity, 0); + } + + public Map getTrappings() { + return Collections.unmodifiableMap(trappings); + } + public void trackPlayTime(float deltaTime) { playTime += deltaTime; } diff --git a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java index 0042bb58..15282a7f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java @@ -33,6 +33,7 @@ public enum ItemUseType { FLY, MULTI, NOTE(new NoteInteraction()), + PET, PLENTY, PROTECTED, PUBLIC, diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java index d024ee2c..aec3f4f2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java @@ -11,6 +11,7 @@ import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemGroup; +import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; import brainwine.gameserver.item.ModType; import brainwine.gameserver.server.PlayerRequest; @@ -118,15 +119,63 @@ public void process(Player player) { processCustomPlace(zone, player); } - // Process burial if item placed is a gravestone + // Misc processing if(item.getGroup() == ItemGroup.GRAVESTONE) { processBurial(zone, player); + } else if(item.getGroup() == ItemGroup.CAGE) { + processTrapping(zone, player); } } + private void processTrapping(Zone zone, Player player) { + // Check bounds + if(x <= 0 || x + 1 >= zone.getWidth() || y <= 0 || y + 1 >= zone.getHeight()) { + return; + } + + // Do nothing if cage is not surrounded by whole blocks + if(!zone.isBlockWhole(x - 1, y - 1) || !zone.isBlockWhole(x, y - 1) || !zone.isBlockWhole(x + 1, y - 1) || !zone.isBlockWhole(x - 1, y) || !zone.isBlockWhole(x + 1, y) + || !zone.isBlockWhole(x - 1, y + 1) || !zone.isBlockWhole(x, y + 1) || !zone.isBlockWhole(x + 1, y + 1)) { + return; + } + + // Find random trappable entity at this location + // TODO we have to do an isDead() check here because dead NPCs aren't always cleared immediately + Npc entity = zone.getNpcs().stream() + .filter(npc -> !npc.isDead() && !npc.isArtificial() && npc.getBlockX() == x && npc.getBlockY() == y && npc.getConfig().isTrappable()) + .findFirst().orElse(null); + + // Do nothing if no eligible entity was found + if(entity == null) { + return; + } + + EntityConfig config = entity.getConfig(); + + // Try to turn entity into a pet cage + if(item.hasUse(ItemUseType.PET)) { + // Don't waste it if entity has no pet variant + if(!config.hasTrappablePetItem()) { + return; + } + + entity.setHealth(0.0F); + zone.updateBlock(x, y, layer, 0); + player.getInventory().addItem(config.getTrappablePetItem(), true); + player.getStatistics().trackTrapping(config); + return; + } + + // Otherwise, kill the entity and place some fur + // TODO v2 stores the quantity in the mod of "piled" items, but this functionality is not implemented here at all! + entity.attack(player, item, entity.getHealth(), DamageType.ACID, true); + zone.updateBlock(x, y, Layer.FRONT, "ground/fur"); + player.getStatistics().trackTrapping(config); + } + private void processBurial(Zone zone, Player player) { // Check bounds - if(x <= 0 || x + 2 > zone.getWidth() || y + 2 > zone.getHeight()) { + if(x <= 0 || x + 2 >= zone.getWidth() || y + 2 >= zone.getHeight()) { return; } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 7d559f93..a23d0400 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -477,6 +477,10 @@ public void explodeLiquid(int x, int y, int range, Item liquid) { } } + public boolean isBlockWhole(int x, int y) { + return areCoordinatesInBounds(x, y) && getBlock(x, y).getFrontItem().isWhole(); + } + public boolean isBlockEarthy(int x, int y) { return areCoordinatesInBounds(x, y) && getBlock(x, y).getFrontItem().isEarthy(); } From 5de602f0df4c2a33fb72f88a8c308bd9afa194a5 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 3 Mar 2024 23:35:05 +0100 Subject: [PATCH 039/176] Update chunk data format --- .../server/requests/BlocksRequest.java | 7 + .../java/brainwine/gameserver/zone/Chunk.java | 10 + .../gameserver/zone/ChunkManager.java | 179 +++++++++++------- 3 files changed, 127 insertions(+), 69 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlocksRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlocksRequest.java index 706a8bb8..3a00167b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlocksRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlocksRequest.java @@ -47,6 +47,13 @@ public void process(Player player) { } Chunk chunk = zone.getChunk(index); + + // Kick player if chunk is null (load failure) + if(chunk == null) { + player.kick("Chunk load failure."); + return; + } + chunks.add(chunk); metaBlocks.addAll(zone.getLocalMetaBlocksInChunk(index)); player.addActiveChunk(index); diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Chunk.java b/gameserver/src/main/java/brainwine/gameserver/zone/Chunk.java index 954ddd70..e594e62d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Chunk.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Chunk.java @@ -16,6 +16,7 @@ public class Chunk { private final int width; private final int height; private final Block[] blocks; + private long saveTime; private boolean modified; @ConstructorProperties({"x", "y", "width", "height", "blocks"}) @@ -83,4 +84,13 @@ private boolean isIndexInBounds(int index) { public Block[] getBlocks() { return blocks; } + + public void setSaveTime(long saveTime) { + this.saveTime = saveTime; + } + + @JsonIgnore + public long getSaveTime() { + return saveTime; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ChunkManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/ChunkManager.java index d299dd39..95d77cbd 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ChunkManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ChunkManager.java @@ -17,11 +17,10 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.zip.DataFormatException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.msgpack.core.MessagePack; -import org.msgpack.core.MessageUnpacker; import org.msgpack.jackson.dataformat.MessagePackFactory; import com.fasterxml.jackson.databind.ObjectMapper; @@ -34,66 +33,101 @@ public class ChunkManager { + public static final int FILE_SIGNATURE = 0x44574344; + public static final int FILE_HEADER_SIZE = 64; + public static final int LATEST_FILE_VERSION = 0x00000001; + public static final int DEFAULT_CHUNK_ALLOC_SIZE = 2048; + public static final int CHUNK_HEADER_SIZE = 32; + public static final byte[] FILE_HEADER_PADDING = new byte[FILE_HEADER_SIZE - 12]; + public static final byte[] CHUNK_HEADER_PADDING = new byte[CHUNK_HEADER_SIZE - 12]; private static final Logger logger = LogManager.getLogger(); - private static final int allocSize = 2048; private static final ObjectMapper mapper = new ObjectMapper(new MessagePackFactory()) .registerModule(new SimpleModule() .addDeserializer(Block.class, BlockDeserializer.INSTANCE) .addSerializer(BlockSerializer.INSTANCE)); private final Map chunks = new HashMap<>(); private final Zone zone; - private final File blocksFile; private RandomAccessFile file; - private int dataOffset; + private int allocSize; public ChunkManager(Zone zone) { this.zone = zone; - blocksFile = new File(zone.getDirectory(), "blocks.dat"); - File legacyBlocksFile = new File(zone.getDirectory(), "blocks"); + } + + private void initialize() throws IOException, DataFormatException { + // Do nothing if already initialized + if(file != null) { + return; + } + + File dataDirectory = zone.getDirectory(); + File chunksFileV1 = new File(dataDirectory, "blocks"); // Legacy version (no longer supported) + File chunksFileV2 = new File(dataDirectory, "blocks.dat"); // Previous version + File chunksFile = new File(dataDirectory, "chunks.bin"); // Latest version - if(!blocksFile.exists() && legacyBlocksFile.exists()) { - logger.info(SERVER_MARKER, "Updating blocks file for zone {} ...", zone.getDocumentId()); - DataInputStream inputStream = null; - DataOutputStream outputStream = null; + // Check outdated legacy format + if(!chunksFileV2.exists() && chunksFileV1.exists()) { + throw new IOException("Chunk data is outdated. Please try to load this zone with an older server version to update it."); + } + + // Load or initialize header data + if(chunksFile.exists()) { + try(DataInputStream inputStream = new DataInputStream(new FileInputStream(chunksFile))) { + // Check file signature + if(inputStream.readInt() != FILE_SIGNATURE) { + throw new IOException("Invalid file signature"); + } + + int fileVersion = inputStream.readInt(); + allocSize = inputStream.readInt(); + inputStream.skip(FILE_HEADER_PADDING.length); + + // Update chunk data if necessary + if(fileVersion != LATEST_FILE_VERSION) { + throw new IOException("Invalid file version"); // Throw exception for now since there is only one version + } + } + } else { + allocSize = DEFAULT_CHUNK_ALLOC_SIZE; - try { - inputStream = new DataInputStream(new FileInputStream(legacyBlocksFile)); - outputStream = new DataOutputStream(new FileOutputStream(blocksFile)); - int chunkCount = zone.getChunkCount(); + try(DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(chunksFile))) { + outputStream.writeInt(FILE_SIGNATURE); + outputStream.writeInt(LATEST_FILE_VERSION); + outputStream.writeInt(allocSize); + outputStream.write(FILE_HEADER_PADDING); - for(int i = 0; i < chunkCount; i++) { - short length = inputStream.readShort(); - byte[] chunkBytes = new byte[length]; - inputStream.read(chunkBytes); - inputStream.skipBytes(2048 - length - 2); - chunkBytes = ZipUtils.inflateBytes(chunkBytes); - MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(chunkBytes); - unpacker.unpackArrayHeader(); - int x = unpacker.unpackInt(); - int y = unpacker.unpackInt(); - int width = unpacker.unpackInt(); - int height = unpacker.unpackInt(); - Block[] blocks = new Block[unpacker.unpackArrayHeader() / 3]; + // Update chunk data from previous version if it is present + if(chunksFileV2.exists()) { + logger.info(SERVER_MARKER, "Updating chunk data for zone {} ...", zone.getDocumentId()); + int chunkCount = zone.getChunkCount(); - for(int j = 0; j < blocks.length; j++) { - blocks[j] = new Block(unpacker.unpackInt(), unpacker.unpackInt(), unpacker.unpackInt()); + try(DataInputStream inputStream = new DataInputStream(new FileInputStream(chunksFileV2))) { + long now = System.currentTimeMillis(); + + for(int i = 0; i < chunkCount; i++) { + byte[] chunkBytes = inputStream.readNBytes(inputStream.readShort()); + inputStream.skip(DEFAULT_CHUNK_ALLOC_SIZE - chunkBytes.length - 2); // Skip reserved chunk space + + // Write chunk header + outputStream.writeLong(now); // Save time + outputStream.writeInt(chunkBytes.length); + outputStream.write(CHUNK_HEADER_PADDING); + + // Write chunk data + outputStream.write(chunkBytes); + + // Write chunk padding + if(i + 1 < chunkCount) { + outputStream.write(new byte[allocSize - chunkBytes.length - CHUNK_HEADER_SIZE]); + } + } } - - unpacker.close(); - byte[] bytes = ZipUtils.deflateBytes(mapper.writeValueAsBytes(new Chunk(x, y, width, height, blocks))); - outputStream.writeShort(bytes.length); - outputStream.write(bytes); - outputStream.write(new byte[allocSize - bytes.length - 2]); } - - inputStream.close(); - outputStream.close(); - } catch(Exception e) { - logger.error(SERVER_MARKER, "Could not update blocks file for zone {}", zone.getDocumentId(), e); } - - legacyBlocksFile.delete(); } + + // Create random access file stream + file = new RandomAccessFile(chunksFile, "rw"); } protected void closeStream() { @@ -112,10 +146,7 @@ public void saveChunks() { List inactiveChunks = new ArrayList<>(); for(Chunk chunk : chunks.values()) { - if(chunk.isModified()) { - saveChunk(chunk); - } - + saveChunk(chunk); boolean active = false; for(Player player : zone.getPlayers()) { @@ -140,35 +171,40 @@ private void saveChunk(Chunk chunk) { int index = zone.getChunkIndex(chunk.getX(), chunk.getY()); try { - if(file == null) { - file = new RandomAccessFile(blocksFile, "rw"); - } + initialize(); + file.seek(FILE_HEADER_SIZE + index * allocSize); + file.writeLong(System.currentTimeMillis()); // Write save time - byte[] bytes = ZipUtils.deflateBytes(mapper.writeValueAsBytes(chunk)); - - if(bytes.length > allocSize) { - throw new IOException("WARNING: bigger than alloc size: " + bytes.length); + // Write block data if chunk has been modified + if(chunk.isModified()) { + byte[] bytes = ZipUtils.deflateBytes(mapper.writeValueAsBytes(chunk)); + + // TODO reformat entire file with bigger alloc size + if(bytes.length > allocSize - CHUNK_HEADER_SIZE) { + throw new IOException("WARNING: bigger than alloc size: " + bytes.length); + } + + file.writeInt(bytes.length); + file.write(CHUNK_HEADER_PADDING); + file.write(bytes); + chunk.setModified(false); } - - file.seek(dataOffset + index * allocSize); - file.writeShort(bytes.length); - file.write(bytes); - chunk.setModified(false); - } catch (IOException e) { + } catch(Exception e) { logger.error(SERVER_MARKER, "Could not save chunk {} of zone {}", index, zone.getDocumentId(), e); } } private Chunk loadChunk(int index) { try { - if(file == null) { - file = new RandomAccessFile(blocksFile, "rw"); - } - - file.seek(dataOffset + index * allocSize); - byte[] bytes = new byte[file.readShort()]; + initialize(); + file.seek(FILE_HEADER_SIZE + index * allocSize); + long saveTime = file.readLong(); + byte[] bytes = new byte[file.readInt()]; + file.skipBytes(CHUNK_HEADER_PADDING.length); file.read(bytes); - return mapper.readValue(ZipUtils.inflateBytes(bytes), Chunk.class); + Chunk chunk = mapper.readValue(ZipUtils.inflateBytes(bytes), Chunk.class); + chunk.setSaveTime(saveTime); + return chunk; } catch(Exception e) { logger.error(SERVER_MARKER, "Could not load chunk {} of zone {}", index, zone.getDocumentId(), e); } @@ -237,10 +273,15 @@ public Chunk getChunk(int index) { Chunk chunk = chunks.get(index); + // Load chunk if it isn't cached if(chunk == null) { chunk = loadChunk(index); - chunks.put(index, chunk); - zone.onChunkLoaded(chunk); + + // Index chunk if it was loaded successfully + if(chunk != null) { + chunks.put(index, chunk); + zone.onChunkLoaded(chunk); + } } return chunk; From 75b12918187641b184646d2724ae653bc4e727bd Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 3 Mar 2024 23:49:32 +0100 Subject: [PATCH 040/176] Fix oopsie --- .../java/brainwine/gameserver/zone/ChunkManager.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ChunkManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/ChunkManager.java index 95d77cbd..4417c717 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ChunkManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ChunkManager.java @@ -100,12 +100,13 @@ private void initialize() throws IOException, DataFormatException { if(chunksFileV2.exists()) { logger.info(SERVER_MARKER, "Updating chunk data for zone {} ...", zone.getDocumentId()); int chunkCount = zone.getChunkCount(); + long now = System.currentTimeMillis(); - try(DataInputStream inputStream = new DataInputStream(new FileInputStream(chunksFileV2))) { - long now = System.currentTimeMillis(); - + try(DataInputStream inputStream = new DataInputStream(new FileInputStream(chunksFileV2))) { for(int i = 0; i < chunkCount; i++) { - byte[] chunkBytes = inputStream.readNBytes(inputStream.readShort()); + // Read chunk data + byte[] chunkBytes = new byte[inputStream.readShort()]; + inputStream.read(chunkBytes); inputStream.skip(DEFAULT_CHUNK_ALLOC_SIZE - chunkBytes.length - 2); // Skip reserved chunk space // Write chunk header From 59e321292ab85e0e4c8d782bfaaa2a7c3ce66fa3 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 7 Mar 2024 00:21:34 +0100 Subject: [PATCH 041/176] Test --- .../java/brainwine/gameserver/entity/player/PlayerManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerManager.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerManager.java index 463ed3f5..1b4766bf 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerManager.java @@ -21,7 +21,7 @@ public class PlayerManager { // TODO check platforms as well - public static final List SUPPORTED_VERSIONS = Arrays.asList("1.12.1", "2.11.0.1", "2.11.1", "3.13.1"); + public static final List SUPPORTED_VERSIONS = Arrays.asList("1.13.3", "2.11.0.1", "2.11.1", "3.13.1"); private static final Logger logger = LogManager.getLogger(); private final Map playersById = new HashMap<>(); private final Map playersByName = new HashMap<>(); From 056e0631ac3c479ea6030fba3b15775a917cda24 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 9 Mar 2024 19:53:50 +0100 Subject: [PATCH 042/176] Add account lock remover for MacOS --- src/main/java/brainwine/gui/MainView.java | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/java/brainwine/gui/MainView.java b/src/main/java/brainwine/gui/MainView.java index 85899024..1af4c3d0 100644 --- a/src/main/java/brainwine/gui/MainView.java +++ b/src/main/java/brainwine/gui/MainView.java @@ -29,6 +29,7 @@ import brainwine.util.DesktopUtils; import brainwine.util.OperatingSystem; import brainwine.util.ProcessResult; +import brainwine.util.ProcessUtils; import brainwine.util.RegistryKey; import brainwine.util.RegistryUtils; import brainwine.util.SwingUtils; @@ -65,11 +66,7 @@ public MainView(Bootstrap bootstrap) { // Menu JMenuBar menuBar = new JMenuBar(); JMenu helpMenu = new JMenu("Help"); - - if(OperatingSystem.isWindows()) { - helpMenu.add(SwingUtils.createAction("Clear Account Lock", this::showAccountLockPrompt)); - } - + helpMenu.add(SwingUtils.createAction("Clear Account Lock", this::showAccountLockPrompt)); helpMenu.add(SwingUtils.createAction("GitHub", () -> DesktopUtils.browseUrl(GITHUB_REPOSITORY_URL))); menuBar.add(helpMenu); @@ -135,12 +132,16 @@ private void showAccountLockPrompt() { if(clearAccountLock()) { JOptionPane.showMessageDialog(frame, "Account lock removed. Register your account next time."); } else { - JOptionPane.showMessageDialog(frame, "Failed to remove account lock.", "Error", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(frame, "Could not remove account lock.\nEither there is no account lock, or an error has occured.", "Error", JOptionPane.ERROR_MESSAGE); } } } private boolean clearAccountLock() { + return OperatingSystem.isWindows() ? clearAccountLockWindows() : OperatingSystem.isMacOS() ? clearAccountLockMacOS() : false; + } + + private boolean clearAccountLockWindows() { ProcessResult queryResult = RegistryUtils.query(DEEPWORLD_PLAYERPREFS, "playerLock*"); if(queryResult.wasSuccessful()) { @@ -151,11 +152,15 @@ private boolean clearAccountLock() { ProcessResult deleteResult = RegistryUtils.delete(DEEPWORLD_PLAYERPREFS, name); return deleteResult.wasSuccessful(); } else { - // Might as well. - return true; + return false; } } return false; } + + // A bit simpler but it should to the trick just fine. + private boolean clearAccountLockMacOS() { + return ProcessUtils.executeCommand("defaults delete com.bytebin.deepworld playerLocked").wasSuccessful(); + } } From 4303c338a55957b43f9507aeaa40db5324cd3631 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Fri, 26 Apr 2024 21:36:46 +0200 Subject: [PATCH 043/176] Automatic default zone sizes --- .../src/main/java/brainwine/gameserver/zone/ZoneManager.java | 2 +- .../java/brainwine/gameserver/zone/gen/ZoneGenerator.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java index 38608eef..f8510c5e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java @@ -65,7 +65,7 @@ public void tryGenerateDefaultZone() { generator = ZoneGenerator.getDefaultZoneGenerator(); } - Zone zone = generator.generateZone(Biome.PLAIN, 2000, 600); + Zone zone = generator.generateZone(Biome.PLAIN); addZone(zone); } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java index 542a5267..77a2573f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java @@ -122,7 +122,7 @@ public Zone generateZone() { } public Zone generateZone(Biome biome) { - return generateZone(biome, 2000, 600); + return generateZone(biome, biome == Biome.DEEP ? 1200 : 2000, biome == Biome.DEEP ? 1000 : 600); } public Zone generateZone(Biome biome, int width, int height) { @@ -165,7 +165,7 @@ public void generateZoneAsync(Consumer callback) { } public void generateZoneAsync(Biome biome, Consumer callback) { - generateZoneAsync(biome, 2000, 600, callback); + generateZoneAsync(biome, biome == Biome.DEEP ? 1200 : 2000, biome == Biome.DEEP ? 1000 : 600, callback); } public void generateZoneAsync(Biome biome, int width, int height, Consumer callback) { From a58575369bce67cd1cc1aea121e170889c018531 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Fri, 26 Apr 2024 22:59:57 +0200 Subject: [PATCH 044/176] Refactor commands --- .../gameserver/GameConfiguration.java | 2 +- .../java/brainwine/gameserver/GameServer.java | 4 +- .../gameserver/annotations/CommandInfo.java | 15 ++ .../brainwine/gameserver/command/Command.java | 24 --- .../gameserver/command/CommandManager.java | 182 ------------------ .../command/commands/RickrollCommand.java | 56 ------ .../command/commands/StopCommand.java | 33 ---- .../gameserver/commands/Command.java | 11 ++ .../CommandExecutor.java | 2 +- .../gameserver/commands/CommandManager.java | 148 ++++++++++++++ .../{command => }/commands/HelpCommand.java | 34 ++-- .../commands/RegisterCommand.java | 15 +- .../{command => }/commands/SayCommand.java | 16 +- .../{command => }/commands/ThinkCommand.java | 16 +- .../admin}/AcidityCommand.java | 18 +- .../admin}/AdminCommand.java | 18 +- .../admin}/BanCommand.java | 23 +-- .../admin}/BroadcastCommand.java | 23 +-- .../admin}/EntityCommand.java | 18 +- .../admin}/ExperienceCommand.java | 23 +-- .../admin}/ExportCommand.java | 18 +- .../admin}/GenerateZoneCommand.java | 23 +-- .../admin}/GiveCommand.java | 18 +- .../admin}/HealthCommand.java | 23 +-- .../admin}/ImportCommand.java | 18 +- .../admin}/KickCommand.java | 18 +- .../admin}/LevelCommand.java | 23 +-- .../admin}/MuteCommand.java | 23 +-- .../admin}/PlayerIdCommand.java | 18 +- .../admin}/PositionCommand.java | 23 +-- .../admin}/PrefabListCommand.java | 23 +-- .../admin}/SeedCommand.java | 18 +- .../admin}/SettleLiquidsCommand.java | 22 +-- .../admin}/SkillPointsCommand.java | 23 +-- .../commands/admin/StopCommand.java | 25 +++ .../admin}/TeleportCommand.java | 23 +-- .../admin}/TimeCommand.java | 18 +- .../admin}/UnbanCommand.java | 23 +-- .../admin}/UnmuteCommand.java | 18 +- .../admin}/WeatherCommand.java | 18 +- .../admin}/ZoneIdCommand.java | 18 +- .../gameserver/entity/player/Player.java | 2 +- .../server/requests/ChatRequest.java | 2 +- .../server/requests/ConsoleRequest.java | 2 +- src/main/java/brainwine/ServerThread.java | 2 +- 45 files changed, 365 insertions(+), 758 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/annotations/CommandInfo.java delete mode 100644 gameserver/src/main/java/brainwine/gameserver/command/Command.java delete mode 100644 gameserver/src/main/java/brainwine/gameserver/command/CommandManager.java delete mode 100644 gameserver/src/main/java/brainwine/gameserver/command/commands/RickrollCommand.java delete mode 100644 gameserver/src/main/java/brainwine/gameserver/command/commands/StopCommand.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/commands/Command.java rename gameserver/src/main/java/brainwine/gameserver/{command => commands}/CommandExecutor.java (83%) create mode 100644 gameserver/src/main/java/brainwine/gameserver/commands/CommandManager.java rename gameserver/src/main/java/brainwine/gameserver/{command => }/commands/HelpCommand.java (72%) rename gameserver/src/main/java/brainwine/gameserver/{command => }/commands/RegisterCommand.java (85%) rename gameserver/src/main/java/brainwine/gameserver/{command => }/commands/SayCommand.java (75%) rename gameserver/src/main/java/brainwine/gameserver/{command => }/commands/ThinkCommand.java (75%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/AcidityCommand.java (79%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/AdminCommand.java (82%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/BanCommand.java (84%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/BroadcastCommand.java (70%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/EntityCommand.java (80%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/ExperienceCommand.java (81%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/ExportCommand.java (90%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/GenerateZoneCommand.java (89%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/GiveCommand.java (90%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/HealthCommand.java (81%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/ImportCommand.java (83%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/KickCommand.java (80%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/LevelCommand.java (81%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/MuteCommand.java (83%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/PlayerIdCommand.java (78%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/PositionCommand.java (57%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/PrefabListCommand.java (76%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/SeedCommand.java (79%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/SettleLiquidsCommand.java (63%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/SkillPointsCommand.java (81%) create mode 100644 gameserver/src/main/java/brainwine/gameserver/commands/admin/StopCommand.java rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/TeleportCommand.java (73%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/TimeCommand.java (84%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/UnbanCommand.java (75%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/UnmuteCommand.java (79%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/WeatherCommand.java (82%) rename gameserver/src/main/java/brainwine/gameserver/{command/commands => commands/admin}/ZoneIdCommand.java (79%) diff --git a/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java b/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java index 60740dbb..f356533f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java +++ b/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java @@ -24,7 +24,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; -import brainwine.gameserver.command.CommandManager; +import brainwine.gameserver.commands.CommandManager; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.entity.player.Skill; import brainwine.gameserver.item.Item; diff --git a/gameserver/src/main/java/brainwine/gameserver/GameServer.java b/gameserver/src/main/java/brainwine/gameserver/GameServer.java index d7df4f1f..a115f66c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/GameServer.java +++ b/gameserver/src/main/java/brainwine/gameserver/GameServer.java @@ -9,8 +9,8 @@ import org.apache.logging.log4j.Logger; import brainwine.gameserver.achievements.AchievementManager; -import brainwine.gameserver.command.CommandExecutor; -import brainwine.gameserver.command.CommandManager; +import brainwine.gameserver.commands.CommandExecutor; +import brainwine.gameserver.commands.CommandManager; import brainwine.gameserver.entity.EntityRegistry; import brainwine.gameserver.entity.player.NotificationType; import brainwine.gameserver.entity.player.PlayerManager; diff --git a/gameserver/src/main/java/brainwine/gameserver/annotations/CommandInfo.java b/gameserver/src/main/java/brainwine/gameserver/annotations/CommandInfo.java new file mode 100644 index 00000000..9dd4d137 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/annotations/CommandInfo.java @@ -0,0 +1,15 @@ +package brainwine.gameserver.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface CommandInfo { + + public String name(); + public String description(); + public String[] aliases() default {}; +} diff --git a/gameserver/src/main/java/brainwine/gameserver/command/Command.java b/gameserver/src/main/java/brainwine/gameserver/command/Command.java deleted file mode 100644 index 3c42d26b..00000000 --- a/gameserver/src/main/java/brainwine/gameserver/command/Command.java +++ /dev/null @@ -1,24 +0,0 @@ -package brainwine.gameserver.command; - -public abstract class Command { - - public abstract void execute(CommandExecutor executor, String[] args); - - public abstract String getName(); - - public String[] getAliases() { - return null; - } - - public String getDescription() { - return "No description for this command"; - } - - public String getUsage(CommandExecutor executor) { - return "/" + getName(); - } - - public boolean canExecute(CommandExecutor executor) { - return true; - } -} diff --git a/gameserver/src/main/java/brainwine/gameserver/command/CommandManager.java b/gameserver/src/main/java/brainwine/gameserver/command/CommandManager.java deleted file mode 100644 index 2a731f29..00000000 --- a/gameserver/src/main/java/brainwine/gameserver/command/CommandManager.java +++ /dev/null @@ -1,182 +0,0 @@ -package brainwine.gameserver.command; - -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; -import static brainwine.shared.LogMarkers.SERVER_MARKER; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import brainwine.gameserver.command.commands.AcidityCommand; -import brainwine.gameserver.command.commands.AdminCommand; -import brainwine.gameserver.command.commands.BanCommand; -import brainwine.gameserver.command.commands.BroadcastCommand; -import brainwine.gameserver.command.commands.EntityCommand; -import brainwine.gameserver.command.commands.ExperienceCommand; -import brainwine.gameserver.command.commands.ExportCommand; -import brainwine.gameserver.command.commands.GenerateZoneCommand; -import brainwine.gameserver.command.commands.GiveCommand; -import brainwine.gameserver.command.commands.HealthCommand; -import brainwine.gameserver.command.commands.HelpCommand; -import brainwine.gameserver.command.commands.ImportCommand; -import brainwine.gameserver.command.commands.KickCommand; -import brainwine.gameserver.command.commands.LevelCommand; -import brainwine.gameserver.command.commands.MuteCommand; -import brainwine.gameserver.command.commands.PlayerIdCommand; -import brainwine.gameserver.command.commands.PositionCommand; -import brainwine.gameserver.command.commands.PrefabListCommand; -import brainwine.gameserver.command.commands.RegisterCommand; -import brainwine.gameserver.command.commands.RickrollCommand; -import brainwine.gameserver.command.commands.SayCommand; -import brainwine.gameserver.command.commands.SeedCommand; -import brainwine.gameserver.command.commands.SettleLiquidsCommand; -import brainwine.gameserver.command.commands.SkillPointsCommand; -import brainwine.gameserver.command.commands.StopCommand; -import brainwine.gameserver.command.commands.TeleportCommand; -import brainwine.gameserver.command.commands.ThinkCommand; -import brainwine.gameserver.command.commands.TimeCommand; -import brainwine.gameserver.command.commands.UnbanCommand; -import brainwine.gameserver.command.commands.UnmuteCommand; -import brainwine.gameserver.command.commands.WeatherCommand; -import brainwine.gameserver.command.commands.ZoneIdCommand; -import brainwine.gameserver.entity.player.Player; - -public class CommandManager { - - public static final String CUSTOM_COMMAND_PREFIX = "!"; // TODO configurable - private static final Logger logger = LogManager.getLogger(); - private static final Map commands = new HashMap<>(); - private static final Map aliases = new HashMap<>(); - private static boolean initialized = false; - - public static void init() { - if(initialized) { - logger.warn(SERVER_MARKER, "CommandManager is already initialized - skipping!"); - return; - } - - registerCommands(); - initialized = true; - } - - private static void registerCommands() { - logger.info(SERVER_MARKER, "Registering commands ..."); - registerCommand(new StopCommand()); - registerCommand(new RegisterCommand()); - registerCommand(new TeleportCommand()); - registerCommand(new KickCommand()); - registerCommand(new MuteCommand()); - registerCommand(new UnmuteCommand()); - registerCommand(new BanCommand()); - registerCommand(new UnbanCommand()); - registerCommand(new SayCommand()); - registerCommand(new ThinkCommand()); - registerCommand(new BroadcastCommand()); - registerCommand(new PlayerIdCommand()); - registerCommand(new ZoneIdCommand()); - registerCommand(new AdminCommand()); - registerCommand(new HelpCommand()); - registerCommand(new GiveCommand()); - registerCommand(new GenerateZoneCommand()); - registerCommand(new SeedCommand()); - registerCommand(new PrefabListCommand()); - registerCommand(new ExportCommand()); - registerCommand(new ImportCommand()); - registerCommand(new PositionCommand()); - registerCommand(new RickrollCommand()); - registerCommand(new EntityCommand()); - registerCommand(new HealthCommand()); - registerCommand(new ExperienceCommand()); - registerCommand(new LevelCommand()); - registerCommand(new SkillPointsCommand()); - registerCommand(new SettleLiquidsCommand()); - registerCommand(new WeatherCommand()); - registerCommand(new AcidityCommand()); - registerCommand(new TimeCommand()); - } - - public static void executeCommand(CommandExecutor executor, String commandLine) { - if(commandLine.isEmpty()) { - return; - } - - commandLine.trim().replaceAll(" +", " "); - String[] sections = commandLine.split(" ", 2); - - if(sections.length == 0) { - return; - } - - String name = sections[0]; - String[] args = sections.length > 1 ? sections[1].split(" ") : new String[0]; - executeCommand(executor, name, args); - } - - public static void executeCommand(CommandExecutor executor, String commandName, String[] args) { - if(!(executor instanceof Player) && commandName.startsWith(CUSTOM_COMMAND_PREFIX) || commandName.startsWith("/")) { - commandName = commandName.substring(1); - } - - Command command = getCommand(commandName, true); - - if(command == null || !command.canExecute(executor)) { - executor.notify("Unknown command. Type '/help' for a list of commands.", SYSTEM); - return; - } - - if(executor instanceof Player) { - Player player = (Player)executor; - logger.info(SERVER_MARKER, "{} used command '/{}'", player.getName(), commandName + (args.length == 0 ? "" : " " + String.join(" ", args))); - } - - command.execute(executor, args); - } - - public static void registerCommand(Command command) { - String name = command.getName(); - - if(commands.containsKey(name)) { - logger.warn(SERVER_MARKER, "Attempted to register duplicate command {} with name {}", command.getClass(), name); - return; - } - - commands.put(name, command); - String[] aliases = command.getAliases(); - - if(aliases != null) { - for(String alias : aliases) { - if(commands.containsKey(alias) || CommandManager.aliases.containsKey(alias)) { - logger.warn(SERVER_MARKER, "Duplicate alias {} for command {}", alias, command.getClass()); - continue; - } - - CommandManager.aliases.put(alias, command); - } - } - } - - public static Set getCommandNames() { - Set names = new HashSet<>(); - names.addAll(commands.keySet()); - names.addAll(aliases.keySet()); - return names; - } - - public static Command getCommand(String name) { - return getCommand(name, false); - } - - public static Command getCommand(String name, boolean allowAlias) { - return commands.getOrDefault(name, allowAlias ? aliases.get(name) : null); - } - - public static Collection getCommands() { - return Collections.unmodifiableCollection(commands.values()); - } -} diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/RickrollCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/commands/RickrollCommand.java deleted file mode 100644 index 1113787e..00000000 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/RickrollCommand.java +++ /dev/null @@ -1,56 +0,0 @@ -package brainwine.gameserver.command.commands; - -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; - -import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.server.messages.EventMessage; - -public class RickrollCommand extends Command { - - @Override - public void execute(CommandExecutor executor, String[] args) { - if(args.length < 1) { - executor.notify(String.format("Usage: %s", getUsage(executor)), SYSTEM); - return; - } - - Player player = GameServer.getInstance().getPlayerManager().getPlayer(args[0]); - - if(player == null) { - executor.notify("This player does not exist.", SYSTEM); - return; - } else if(!player.isOnline()) { - executor.notify("This player is offline.", SYSTEM); - return; - } else if(!player.isV3()) { - executor.notify("Cannot open URLs on iOS clients.", SYSTEM); - return; - } - - player.sendMessage(new EventMessage("openUrl", "https://www.youtube.com/watch?v=dQw4w9WgXcQ")); - executor.notify(String.format("Successfully rickrolled %s!", player.getName()), SYSTEM); - } - - @Override - public String getName() { - return "rickroll"; - } - - @Override - public String getDescription() { - return "Makes a player hate you forever."; - } - - @Override - public String getUsage(CommandExecutor executor) { - return "/rickroll "; - } - - @Override - public boolean canExecute(CommandExecutor executor) { - return executor.isAdmin(); - } -} diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/StopCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/commands/StopCommand.java deleted file mode 100644 index b7054adb..00000000 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/StopCommand.java +++ /dev/null @@ -1,33 +0,0 @@ -package brainwine.gameserver.command.commands; - -import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; - -public class StopCommand extends Command { - - @Override - public void execute(CommandExecutor executor, String[] args) { - GameServer.getInstance().stopGracefully(); // YEET!! - } - - @Override - public String getName() { - return "stop"; - } - - @Override - public String[] getAliases() { - return new String[] { "exit", "close", "shutdown" }; - } - - @Override - public String getDescription() { - return "Gracefully shuts down the server after the current tick."; - } - - @Override - public boolean canExecute(CommandExecutor executor) { - return executor.isAdmin(); - } -} diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/Command.java b/gameserver/src/main/java/brainwine/gameserver/commands/Command.java new file mode 100644 index 00000000..c6555970 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/commands/Command.java @@ -0,0 +1,11 @@ +package brainwine.gameserver.commands; + +public abstract class Command { + + public abstract void execute(CommandExecutor executor, String[] args); + public abstract String getUsage(CommandExecutor executor); + + public boolean canExecute(CommandExecutor executor) { + return true; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/command/CommandExecutor.java b/gameserver/src/main/java/brainwine/gameserver/commands/CommandExecutor.java similarity index 83% rename from gameserver/src/main/java/brainwine/gameserver/command/CommandExecutor.java rename to gameserver/src/main/java/brainwine/gameserver/commands/CommandExecutor.java index 9929a1b1..cda63e79 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/CommandExecutor.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/CommandExecutor.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.command; +package brainwine.gameserver.commands; import brainwine.gameserver.entity.player.NotificationType; diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/CommandManager.java b/gameserver/src/main/java/brainwine/gameserver/commands/CommandManager.java new file mode 100644 index 00000000..e82da305 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/commands/CommandManager.java @@ -0,0 +1,148 @@ +package brainwine.gameserver.commands; + +import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.shared.LogMarkers.SERVER_MARKER; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.reflections.Reflections; + +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.entity.player.Player; + +@SuppressWarnings("unchecked") +public class CommandManager { + + public static final String CUSTOM_COMMAND_PREFIX = "!"; // TODO configurable + private static final Logger logger = LogManager.getLogger(); + private static final Map commands = new HashMap<>(); + private static final Map aliases = new HashMap<>(); + private static boolean initialized = false; + + public static void init() { + if(initialized) { + logger.warn(SERVER_MARKER, "CommandManager is already initialized - skipping!"); + return; + } + + registerCommands(); + initialized = true; + } + + private static void registerCommands() { + logger.info(SERVER_MARKER, "Registering commands ..."); + Reflections reflections = new Reflections("brainwine.gameserver.commands"); + Set> classes = reflections.getTypesAnnotatedWith(CommandInfo.class); + + for(Class clazz : classes) { + if(!Command.class.isAssignableFrom(clazz)) { + logger.warn(SERVER_MARKER, "Attempted to register non-command class {}", clazz.getSimpleName()); + continue; + } + + registerCommand((Class)clazz); + } + } + + public static void executeCommand(CommandExecutor executor, String commandLine) { + if(commandLine.isEmpty()) { + return; + } + + commandLine.trim().replaceAll(" +", " "); + String[] sections = commandLine.split(" ", 2); + + if(sections.length == 0) { + return; + } + + String name = sections[0]; + String[] args = sections.length > 1 ? sections[1].split(" ") : new String[0]; + executeCommand(executor, name, args); + } + + public static void executeCommand(CommandExecutor executor, String commandName, String[] args) { + if(!(executor instanceof Player) && commandName.startsWith(CUSTOM_COMMAND_PREFIX) || commandName.startsWith("/")) { + commandName = commandName.substring(1); + } + + Command command = getCommand(commandName, true); + + if(command == null || !command.canExecute(executor)) { + executor.notify("Unknown command. Type '/help' for a list of commands.", SYSTEM); + return; + } + + if(executor instanceof Player) { + Player player = (Player)executor; + logger.info(SERVER_MARKER, "{} used command '/{}'", player.getName(), commandName + (args.length == 0 ? "" : " " + String.join(" ", args))); + } + + command.execute(executor, args); + } + + public static void registerCommand(Class type) { + CommandInfo info = type.getAnnotation(CommandInfo.class); + + if(info == null) { + logger.warn(SERVER_MARKER, "Cannot register command '{}' because it does not have the CommandInfo annotation", type.getSimpleName()); + return; + } + + String name = info.name(); + String[] aliases = info.aliases(); + + if(commands.containsKey(name)) { + logger.warn(SERVER_MARKER, "Attempted to register duplicate command '{}' with name '{}'", type.getSimpleName(), name); + return; + } + + Command command = null; + + try { + command = type.getConstructor().newInstance(); + } catch(ReflectiveOperationException e) { + logger.error("Failed to not instantiate command '{}'", type.getSimpleName(), e); + return; + } + + commands.put(name, command); + + if(aliases != null) { + for(String alias : aliases) { + if(commands.containsKey(alias) || CommandManager.aliases.containsKey(alias)) { + logger.warn(SERVER_MARKER, "Duplicate alias {} for command {}", alias, command.getClass()); + continue; + } + + CommandManager.aliases.put(alias, command); + } + } + } + + public static Set getCommandNames() { + Set names = new HashSet<>(); + names.addAll(commands.keySet()); + names.addAll(aliases.keySet()); + return names; + } + + public static Command getCommand(String name) { + return getCommand(name, false); + } + + public static Command getCommand(String name, boolean allowAlias) { + return commands.getOrDefault(name, allowAlias ? aliases.get(name) : null); + } + + public static Collection getCommands() { + return Collections.unmodifiableCollection(commands.values()); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/HelpCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/HelpCommand.java similarity index 72% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/HelpCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/HelpCommand.java index 2516d580..8c9fcb31 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/HelpCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/HelpCommand.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; @@ -8,16 +8,20 @@ import org.apache.commons.lang3.math.NumberUtils; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; -import brainwine.gameserver.command.CommandManager; +import brainwine.gameserver.annotations.CommandInfo; +@CommandInfo(name = "help", description = "Displays a list of commands.") public class HelpCommand extends Command { @Override public void execute(CommandExecutor executor, String[] args) { List commands = new ArrayList<>(CommandManager.getCommands()); commands.removeIf(command -> !command.canExecute(executor)); + commands.sort((a, b) -> { + CommandInfo info1 = a.getClass().getAnnotation(CommandInfo.class); + CommandInfo info2 = b.getClass().getAnnotation(CommandInfo.class); + return info1.name().compareTo(info2.name()); + }); int pageSize = 8; int pageCount = (int)Math.ceil(commands.size() / (double)pageSize); int page = 1; @@ -41,11 +45,12 @@ public void execute(CommandExecutor executor, String[] args) { return; } - executor.notify(String.format("========== Information about '/%s' ==========", command.getName()), SYSTEM); - executor.notify(String.format("Description: %s", command.getDescription()), SYSTEM); + CommandInfo info = command.getClass().getAnnotation(CommandInfo.class); + executor.notify(String.format("========== Information about '/%s' ==========", info.name()), SYSTEM); + executor.notify(String.format("Description: %s", info.description()), SYSTEM); executor.notify(String.format("Usage: %s", command.getUsage(executor)), SYSTEM); - executor.notify(String.format("Aliases: %s", command.getAliases() == null ? "None :(" - : Arrays.toString(command.getAliases())), SYSTEM); + executor.notify(String.format("Aliases: %s", info.aliases() == null ? "None :(" + : Arrays.toString(info.aliases())), SYSTEM); return; } } @@ -56,19 +61,10 @@ public void execute(CommandExecutor executor, String[] args) { executor.notify(String.format("========== Command List (Page %s of %s) ==========", page, pageCount), SYSTEM); for(Command command : commandsToDisplay) { - executor.notify(String.format("%s - %s", command.getUsage(executor), command.getDescription()), SYSTEM); + CommandInfo info = command.getClass().getAnnotation(CommandInfo.class); + executor.notify(String.format("%s - %s", command.getUsage(executor), info.description()), SYSTEM); } } - - @Override - public String getName() { - return "help"; - } - - @Override - public String getDescription() { - return "Displays command information."; - } @Override public String getUsage(CommandExecutor executor) { diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/RegisterCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/RegisterCommand.java similarity index 85% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/RegisterCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/RegisterCommand.java index f8ff79d1..cab3cf59 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/RegisterCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/RegisterCommand.java @@ -1,16 +1,16 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands; import org.apache.commons.validator.routines.EmailValidator; import org.mindrot.jbcrypt.BCrypt; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; import brainwine.gameserver.dialog.DialogHelper; import brainwine.gameserver.entity.player.Player; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +@CommandInfo(name = "register", description = "Shows a prompt with which you can register your account.") public class RegisterCommand extends Command { @Override @@ -55,13 +55,8 @@ public void execute(CommandExecutor executor, String[] args) { } @Override - public String getName() { - return "register"; - } - - @Override - public String getDescription() { - return "Shows a prompt with which you can register your account."; + public String getUsage(CommandExecutor executor) { + return "/register"; } @Override diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/SayCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/SayCommand.java similarity index 75% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/SayCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/SayCommand.java index e93375b1..63f82d50 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/SayCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/SayCommand.java @@ -1,12 +1,12 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; import brainwine.gameserver.entity.player.ChatType; import brainwine.gameserver.entity.player.Player; +@CommandInfo(name = "say", description = "Shows a speech bubble to nearby players.") public class SayCommand extends Command { @Override @@ -27,16 +27,6 @@ public void execute(CommandExecutor executor, String[] args) { player.getZone().sendChatMessage(player, text, ChatType.SPEECH); } - @Override - public String getName() { - return "say"; - } - - @Override - public String getDescription() { - return "Shows a speech bubble to nearby players."; - } - @Override public String getUsage(CommandExecutor executor) { return "/say "; diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/ThinkCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/ThinkCommand.java similarity index 75% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/ThinkCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/ThinkCommand.java index e4b52401..599aa0a9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/ThinkCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/ThinkCommand.java @@ -1,12 +1,12 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; import brainwine.gameserver.entity.player.ChatType; import brainwine.gameserver.entity.player.Player; +@CommandInfo(name = "think", description = "Shows a thought bubble to nearby players.") public class ThinkCommand extends Command { @Override @@ -27,16 +27,6 @@ public void execute(CommandExecutor executor, String[] args) { player.getZone().sendChatMessage(player, text, ChatType.THOUGHT); } - @Override - public String getName() { - return "think"; - } - - @Override - public String getDescription() { - return "Shows a thought bubble to nearby players."; - } - @Override public String getUsage(CommandExecutor executor) { return "/think "; diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/AcidityCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/AcidityCommand.java similarity index 79% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/AcidityCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/AcidityCommand.java index 77aa5689..2bfc0923 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/AcidityCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/AcidityCommand.java @@ -1,12 +1,14 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.zone.Zone; +@CommandInfo(name = "acidity", description = "Displays or changes the acidity in the current zone.") public class AcidityCommand extends Command { @Override @@ -35,16 +37,6 @@ public void execute(CommandExecutor executor, String[] args) { zone.setAcidity(value); executor.notify(String.format("Acidity has been set to %s in %s.", value, zone.getName()), SYSTEM); } - - @Override - public String getName() { - return "acidity"; - } - - @Override - public String getDescription() { - return "Displays or changes the acidity in the current zone."; - } @Override public String getUsage(CommandExecutor executor) { diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/AdminCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/AdminCommand.java similarity index 82% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/AdminCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/AdminCommand.java index cfce46ce..7106f5fb 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/AdminCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/AdminCommand.java @@ -1,12 +1,14 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; +@CommandInfo(name = "admin", description = "Grants or revokes administrator rights.") public class AdminCommand extends Command { @Override @@ -38,16 +40,6 @@ public void execute(CommandExecutor executor, String[] args) { executor.notify(String.format("Changed administrator status of player %s to %s", target.getName(), admin), SYSTEM); } - @Override - public String getName() { - return "admin"; - } - - @Override - public String getDescription() { - return "Allows you to grant or revoke administrator rights."; - } - @Override public String getUsage(CommandExecutor executor) { return "/admin [true|false]"; diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/BanCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/BanCommand.java similarity index 84% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/BanCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/BanCommand.java index 2296cd8d..d9982db5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/BanCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/BanCommand.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; @@ -7,11 +7,13 @@ import java.util.Arrays; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.util.DateTimeUtils; +@CommandInfo(name = "ban", description = "Bans a player from the server.") public class BanCommand extends Command { @Override @@ -58,21 +60,6 @@ public void execute(CommandExecutor executor, String[] args) { target.getName(), endDate.format(DateTimeFormatter.RFC_1123_DATE_TIME), reason), SYSTEM); } - @Override - public String getName() { - return "ban"; - } - - @Override - public String[] getAliases() { - return new String[] { "banish" }; - } - - @Override - public String getDescription() { - return "Bans a player from the server."; - } - @Override public String getUsage(CommandExecutor executor) { return "/ban [reason]"; diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/BroadcastCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/BroadcastCommand.java similarity index 70% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/BroadcastCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/BroadcastCommand.java index 08c6f8a9..1bb7b253 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/BroadcastCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/BroadcastCommand.java @@ -1,13 +1,15 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.POPUP; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; +@CommandInfo(name = "broadcast", description = "Broadcasts a message to all online players.", aliases = "bc") public class BroadcastCommand extends Command { @Override @@ -26,21 +28,6 @@ public void execute(CommandExecutor executor, String[] args) { executor.notify("Your message has been broadcasted.", POPUP); } - @Override - public String getName() { - return "broadcast"; - } - - @Override - public String[] getAliases() { - return new String[] { "bc" }; - } - - @Override - public String getDescription() { - return "Broadcasts a message to all online players."; - } - @Override public String getUsage(CommandExecutor executor) { return "/broadcast "; diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/EntityCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/EntityCommand.java similarity index 80% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/EntityCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/EntityCommand.java index f52ad8ee..408f7825 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/EntityCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/EntityCommand.java @@ -1,9 +1,10 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.EntityConfig; import brainwine.gameserver.entity.EntityRegistry; import brainwine.gameserver.entity.npc.Npc; @@ -11,6 +12,7 @@ import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.zone.Zone; +@CommandInfo(name = "entity", description = "Spawns an entity at your current location.") public class EntityCommand extends Command { @Override @@ -32,16 +34,6 @@ public void execute(CommandExecutor executor, String[] args) { Zone zone = player.getZone(); zone.spawnEntity(new Npc(zone, config), (int)player.getX(), (int)player.getY(), true); } - - @Override - public String getName() { - return "entity"; - } - - @Override - public String getDescription() { - return "Spawns an entity at your current location."; - } @Override public String getUsage(CommandExecutor executor) { diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/ExperienceCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/ExperienceCommand.java similarity index 81% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/ExperienceCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/ExperienceCommand.java index 3706c4a9..fdd84d7f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/ExperienceCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/ExperienceCommand.java @@ -1,12 +1,14 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; +@CommandInfo(name = "experience", description = "Sets the experience of the target player.", aliases = { "xp", "exp" }) public class ExperienceCommand extends Command { @Override @@ -53,21 +55,6 @@ public void execute(CommandExecutor executor, String[] args) { target.setExperience(experience); executor.notify(String.format("Successfully set %s's experience to %s.", target.getName(), experience), SYSTEM); } - - @Override - public String getName() { - return "experience"; - } - - @Override - public String[] getAliases() { - return new String[] { "xp", "exp" }; - } - - @Override - public String getDescription() { - return "Sets the experience of the target player."; - } @Override public String getUsage(CommandExecutor executor) { diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/ExportCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/ExportCommand.java similarity index 90% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/ExportCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/ExportCommand.java index d4fdb669..399f1f48 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/ExportCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/ExportCommand.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; @@ -6,13 +6,15 @@ import java.util.regex.Pattern; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.prefab.Prefab; import brainwine.gameserver.prefab.PrefabManager; import brainwine.gameserver.zone.Zone; +@CommandInfo(name = "export", description = "Exports a section of a zone to a prefab file.") public class ExportCommand extends Command { public static final Pattern PREFAB_NAME_PATTERN = Pattern.compile("\\w+(/\\w+)*"); @@ -80,16 +82,6 @@ public void execute(CommandExecutor executor, String[] args) { } } - @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 "; diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/GenerateZoneCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/GenerateZoneCommand.java similarity index 89% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/GenerateZoneCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/GenerateZoneCommand.java index 53985f44..0036466b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/GenerateZoneCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/GenerateZoneCommand.java @@ -1,14 +1,16 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.zone.Biome; import brainwine.gameserver.zone.Zone; import brainwine.gameserver.zone.gen.ZoneGenerator; +@CommandInfo(name = "genzone", description = "Asynchronously generates a new zone.", aliases = "generate") public class GenerateZoneCommand extends Command { public static final int MIN_WIDTH = 200; @@ -84,21 +86,6 @@ public void execute(CommandExecutor executor, String[] args) { } }); } - - @Override - public String getName() { - return "genzone"; - } - - @Override - public String[] getAliases() { - return new String[] { "generate" }; - } - - @Override - public String getDescription() { - return "Asynchronously generates a new zone."; - } @Override public String getUsage(CommandExecutor executor) { diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/GiveCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/GiveCommand.java similarity index 90% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/GiveCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/GiveCommand.java index 56fb90b2..9a0bd92f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/GiveCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/GiveCommand.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; @@ -6,12 +6,14 @@ import java.util.List; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; +@CommandInfo(name = "give", description = "Give or take items from players.") public class GiveCommand extends Command { @Override @@ -84,16 +86,6 @@ public void execute(CommandExecutor executor, String[] args) { executor.notify(String.format("Took %s %s from %s", -quantity, title, target.getName()), SYSTEM); } } - - @Override - public String getName() { - return "give"; - } - - @Override - public String getDescription() { - return "Adds items to a player's inventory."; - } @Override public String getUsage(CommandExecutor executor) { diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/HealthCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/HealthCommand.java similarity index 81% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/HealthCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/HealthCommand.java index 5e84fa39..2e942a86 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/HealthCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/HealthCommand.java @@ -1,12 +1,14 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; +@CommandInfo(name = "health", description = "Sets the target player's health.", aliases = "hp") public class HealthCommand extends Command { @Override @@ -47,21 +49,6 @@ public void execute(CommandExecutor executor, String[] args) { executor.notify(String.format("Set %s's health to %s", target.getName(), target.getHealth()), SYSTEM); } } - - @Override - public String getName() { - return "health"; - } - - @Override - public String[] getAliases() { - return new String[] { "hp" }; - } - - @Override - public String getDescription() { - return "Sets a player's health."; - } @Override public String getUsage(CommandExecutor executor) { diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/ImportCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/ImportCommand.java similarity index 83% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/ImportCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/ImportCommand.java index 7178f867..204ae6cd 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/ImportCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/ImportCommand.java @@ -1,13 +1,15 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.prefab.Prefab; +@CommandInfo(name = "import", description = "Places a prefab at the specified location.") public class ImportCommand extends Command { @Override @@ -46,16 +48,6 @@ public void execute(CommandExecutor executor, String[] args) { player.notify(String.format("Successfully imported '%s' @ [x: %s, y: %s, width: %s, height: %s]", name, x, y, prefab.getWidth(), prefab.getHeight()), SYSTEM); } - - @Override - public String getName() { - return "import"; - } - - @Override - public String getDescription() { - return "Places a prefab at your or a specified location."; - } @Override public String getUsage(CommandExecutor executor) { diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/KickCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/KickCommand.java similarity index 80% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/KickCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/KickCommand.java index 1ba646b8..63b94ab9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/KickCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/KickCommand.java @@ -1,14 +1,16 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; import java.util.Arrays; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; +@CommandInfo(name = "kick", description = "Kicks a player from the server.") public class KickCommand extends Command { @Override @@ -38,16 +40,6 @@ public void execute(CommandExecutor executor, String[] args) { executor.notify("Kicked player " + player.getName() + " for '" + reason + "'", SYSTEM); } - @Override - public String getName() { - return "kick"; - } - - @Override - public String getDescription() { - return "Kicks a player from the server."; - } - @Override public String getUsage(CommandExecutor executor) { return "/kick [reason]"; diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/LevelCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/LevelCommand.java similarity index 81% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/LevelCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/LevelCommand.java index 0e1437a2..1616b81e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/LevelCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/LevelCommand.java @@ -1,12 +1,14 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; +@CommandInfo(name = "level", description = "Sets the level of the target player.", aliases = { "lvl", "lv" }) public class LevelCommand extends Command { @Override @@ -55,21 +57,6 @@ public void execute(CommandExecutor executor, String[] args) { target.setLevel(level); executor.notify(String.format("Successfully set %s's level to %s.", target.getName(), level), SYSTEM); } - - @Override - public String getName() { - return "level"; - } - - @Override - public String[] getAliases() { - return new String[] { "lvl", "lv" }; - } - - @Override - public String getDescription() { - return "Sets the level of the target player."; - } @Override public String getUsage(CommandExecutor executor) { diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/MuteCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/MuteCommand.java similarity index 83% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/MuteCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/MuteCommand.java index 9b0a9732..f32b0a10 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/MuteCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/MuteCommand.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; @@ -7,11 +7,13 @@ import java.util.Arrays; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.util.DateTimeUtils; +@CommandInfo(name = "mute", description = "Mutes a player, preventing them from chatting.", aliases = "silence") public class MuteCommand extends Command { @Override @@ -58,21 +60,6 @@ public void execute(CommandExecutor executor, String[] args) { target.getName(), endDate.format(DateTimeFormatter.RFC_1123_DATE_TIME), reason), SYSTEM); } - @Override - public String getName() { - return "mute"; - } - - @Override - public String[] getAliases() { - return new String[] { "silence", }; - } - - @Override - public String getDescription() { - return "Mutes a player, preventing them from chatting."; - } - @Override public String getUsage(CommandExecutor executor) { return "/mute [reason]"; diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/PlayerIdCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/PlayerIdCommand.java similarity index 78% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/PlayerIdCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/PlayerIdCommand.java index 0b0f8def..158b1f3f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/PlayerIdCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/PlayerIdCommand.java @@ -1,12 +1,14 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; +@CommandInfo(name = "pid", description = "Displays the document id of a player.") public class PlayerIdCommand extends Command { @Override @@ -34,16 +36,6 @@ public void execute(CommandExecutor executor, String[] args) { executor.notify(target.getDocumentId(), SYSTEM); } - @Override - public String getName() { - return "pid"; - } - - @Override - public String getDescription() { - return "Displays the document id of a player."; - } - @Override public String getUsage(CommandExecutor executor) { return String.format("/pid %s", executor instanceof Player ? "[player]" : ""); diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/PositionCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/PositionCommand.java similarity index 57% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/PositionCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/PositionCommand.java index 1ddede5b..e05b7161 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/PositionCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/PositionCommand.java @@ -1,11 +1,13 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; +@CommandInfo(name = "position", description = "Displays the coordinates of the block you are standing on.", aliases = { "pos", "feet", "coords", "location" }) public class PositionCommand extends Command { @Override @@ -13,21 +15,6 @@ public void execute(CommandExecutor executor, String[] args) { Player player = (Player)executor; player.notify(String.format("X: %s Y: %s", (int)player.getX(), (int)player.getY() + 1), SYSTEM); } - - @Override - public String getName() { - return "pos"; - } - - @Override - public String[] getAliases() { - return new String[] { "position", "feet", "coords", "location" }; - } - - @Override - public String getDescription() { - return "Displays the coordinates of the block you are standing on."; - } @Override public String getUsage(CommandExecutor executor) { diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/PrefabListCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/PrefabListCommand.java similarity index 76% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/PrefabListCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/PrefabListCommand.java index 50ca8e3e..dff4e216 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/PrefabListCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/PrefabListCommand.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; @@ -8,10 +8,12 @@ import org.apache.commons.lang3.math.NumberUtils; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.prefab.Prefab; +@CommandInfo(name = "prefabs", description = "Displays a list of all prefabs.") public class PrefabListCommand extends Command { @Override @@ -34,21 +36,6 @@ public void execute(CommandExecutor executor, String[] args) { executor.notify(prefab.getName(), SYSTEM); } } - - @Override - public String getName() { - return "prefabs"; - } - - @Override - public String[] getAliases() { - return new String[] { "prefablist" }; - } - - @Override - public String getDescription() { - return "Displays a list of all prefabs."; - } @Override public String getUsage(CommandExecutor executor) { diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/SeedCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/SeedCommand.java similarity index 79% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/SeedCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/SeedCommand.java index 5135e794..e5e40a0f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/SeedCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/SeedCommand.java @@ -1,13 +1,15 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.zone.Zone; +@CommandInfo(name = "seed", description = "Displays the seed of a zone.") public class SeedCommand extends Command { @Override @@ -35,16 +37,6 @@ public void execute(CommandExecutor executor, String[] args) { executor.notify("Seed: " + target.getSeed(), SYSTEM); } - @Override - public String getName() { - return "seed"; - } - - @Override - public String getDescription() { - return "Displays the seed of a zone."; - } - @Override public String getUsage(CommandExecutor executor) { return String.format("/seed %s", executor instanceof Player ? "[zone]" : ""); diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/SettleLiquidsCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/SettleLiquidsCommand.java similarity index 63% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/SettleLiquidsCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/SettleLiquidsCommand.java index b81d20a5..fa6cf386 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/SettleLiquidsCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/SettleLiquidsCommand.java @@ -1,11 +1,13 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; +@CommandInfo(name = "settle", description = "Settles all liquids in all active chunks in the current zone. Warning - can cause lag!") public class SettleLiquidsCommand extends Command { @Override @@ -19,18 +21,8 @@ public void execute(CommandExecutor executor, String[] args) { } @Override - public String getName() { - return "settleliquids"; - } - - @Override - public String[] getAliases() { - return new String[] {"settle"}; - } - - @Override - public String getDescription() { - return "Settles all liquids in all active chunks in the current zone. Warning - can cause lag!"; + public String getUsage(CommandExecutor executor) { + return "/settle"; } @Override diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/SkillPointsCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/SkillPointsCommand.java similarity index 81% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/SkillPointsCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/SkillPointsCommand.java index f6abefc8..22ccfa75 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/SkillPointsCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/SkillPointsCommand.java @@ -1,12 +1,14 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; +@CommandInfo(name = "skillpoints", description = "Sets the skill points of the target player.", aliases = "points") public class SkillPointsCommand extends Command { @Override @@ -54,21 +56,6 @@ public void execute(CommandExecutor executor, String[] args) { target.notify(String.format("Your skill point count has been set to %s.", amount), SYSTEM); executor.notify(String.format("Successfully set %s's skill point count to %s.", target.getName(), amount), SYSTEM); } - - @Override - public String getName() { - return "skillpoints"; - } - - @Override - public String[] getAliases() { - return new String[] { "points" }; - } - - @Override - public String getDescription() { - return "Sets the skill points of the target player."; - } @Override public String getUsage(CommandExecutor executor) { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/StopCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/StopCommand.java new file mode 100644 index 00000000..b168555a --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/StopCommand.java @@ -0,0 +1,25 @@ +package brainwine.gameserver.commands.admin; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; + +@CommandInfo(name = "stop", description = "Gracefully shuts down the server.", aliases = { "exit", "close", "shutdown" }) +public class StopCommand extends Command { + + @Override + public void execute(CommandExecutor executor, String[] args) { + GameServer.getInstance().stopGracefully(); // YEET!! + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/stop"; + } + + @Override + public boolean canExecute(CommandExecutor executor) { + return executor.isAdmin(); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/TeleportCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/TeleportCommand.java similarity index 73% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/TeleportCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/TeleportCommand.java index 387d0d67..d09952c2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/TeleportCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/TeleportCommand.java @@ -1,11 +1,13 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; +@CommandInfo(name = "teleport", description = "Teleports you to the specified position.", aliases = "tp") public class TeleportCommand extends Command { @Override @@ -35,21 +37,6 @@ public void execute(CommandExecutor executor, String[] args) { player.teleport(x, y); } - @Override - public String getName() { - return "teleport"; - } - - @Override - public String[] getAliases() { - return new String[] { "tp" }; - } - - @Override - public String getDescription() { - return "Teleports you to the specified position."; - } - @Override public String getUsage(CommandExecutor executor) { return "/teleport "; diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/TimeCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/TimeCommand.java similarity index 84% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/TimeCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/TimeCommand.java index e61c24e1..5790328c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/TimeCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/TimeCommand.java @@ -1,12 +1,14 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.zone.Zone; +@CommandInfo(name = "time", description = "Displays or changes the time in the current zone.") public class TimeCommand extends Command { @Override @@ -45,16 +47,6 @@ public void execute(CommandExecutor executor, String[] args) { zone.setTime(value); executor.notify(String.format("Time has been set to %s in %s.", value, zone.getName()), SYSTEM); } - - @Override - public String getName() { - return "time"; - } - - @Override - public String getDescription() { - return "Displays or changes the time in the current zone."; - } @Override public String getUsage(CommandExecutor executor) { diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/UnbanCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/UnbanCommand.java similarity index 75% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/UnbanCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/UnbanCommand.java index eecb4ed1..934f4a2d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/UnbanCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/UnbanCommand.java @@ -1,12 +1,14 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; +@CommandInfo(name = "unban", description = "Unbans a player.", aliases = "pardon") public class UnbanCommand extends Command { @Override @@ -31,21 +33,6 @@ public void execute(CommandExecutor executor, String[] args) { target.unban(executor instanceof Player ? (Player)executor : null); executor.notify(String.format("Player %s has been unbanned.", target.getName()), SYSTEM); } - - @Override - public String getName() { - return "unban"; - } - - @Override - public String[] getAliases() { - return new String[] { "pardon" }; - } - - @Override - public String getDescription() { - return "Unbans a player."; - } @Override public String getUsage(CommandExecutor executor) { diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/UnmuteCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/UnmuteCommand.java similarity index 79% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/UnmuteCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/UnmuteCommand.java index 185a145c..23803c8f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/UnmuteCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/UnmuteCommand.java @@ -1,12 +1,14 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; +@CommandInfo(name = "unmute", description = "Unmutes a player.") public class UnmuteCommand extends Command { @Override @@ -31,16 +33,6 @@ public void execute(CommandExecutor executor, String[] args) { target.unmute(executor instanceof Player ? (Player)executor : null); executor.notify(String.format("Player %s has been unmuted.", target.getName()), SYSTEM); } - - @Override - public String getName() { - return "unmute"; - } - - @Override - public String getDescription() { - return "Unmutes a player."; - } @Override public String getUsage(CommandExecutor executor) { diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/WeatherCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/WeatherCommand.java similarity index 82% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/WeatherCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/WeatherCommand.java index e7027c79..4b8ea6cd 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/WeatherCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/WeatherCommand.java @@ -1,14 +1,16 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.zone.Biome; import brainwine.gameserver.zone.WeatherManager; import brainwine.gameserver.zone.Zone; +@CommandInfo(name = "weather", description = "Displays or changes the weather in the current zone.") public class WeatherCommand extends Command { @Override @@ -32,16 +34,6 @@ public void execute(CommandExecutor executor, String[] args) { zone.getWeatherManager().createRandomRain(dry); executor.notify(String.format("Weather has been %s in %s.", dry ? "cleared" : "made rainy", zone.getName()), SYSTEM); } - - @Override - public String getName() { - return "weather"; - } - - @Override - public String getDescription() { - return "Displays or changes the weather in the current zone."; - } @Override public String getUsage(CommandExecutor executor) { diff --git a/gameserver/src/main/java/brainwine/gameserver/command/commands/ZoneIdCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/ZoneIdCommand.java similarity index 79% rename from gameserver/src/main/java/brainwine/gameserver/command/commands/ZoneIdCommand.java rename to gameserver/src/main/java/brainwine/gameserver/commands/admin/ZoneIdCommand.java index 11bf20a0..c674a8a6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/commands/ZoneIdCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/ZoneIdCommand.java @@ -1,13 +1,15 @@ -package brainwine.gameserver.command.commands; +package brainwine.gameserver.commands.admin; import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.command.Command; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.zone.Zone; +@CommandInfo(name = "zid", description = "Displays the document id of a zone.") public class ZoneIdCommand extends Command { @Override @@ -35,16 +37,6 @@ public void execute(CommandExecutor executor, String[] args) { executor.notify(target.getDocumentId(), SYSTEM); } - @Override - public String getName() { - return "zid"; - } - - @Override - public String getDescription() { - return "Displays the document id of a zone."; - } - @Override public String getUsage(CommandExecutor executor) { return String.format("/zid %s", executor instanceof Player ? "[zone]" : ""); diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java index 72e44ae9..97536a37 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java @@ -22,7 +22,7 @@ import brainwine.gameserver.achievements.Achievement; import brainwine.gameserver.achievements.AchievementManager; import brainwine.gameserver.achievements.JourneymanAchievement; -import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.commands.CommandExecutor; import brainwine.gameserver.dialog.Dialog; import brainwine.gameserver.dialog.DialogListItem; import brainwine.gameserver.dialog.DialogSection; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/ChatRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/ChatRequest.java index 267244b3..230f9322 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/ChatRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/ChatRequest.java @@ -2,7 +2,7 @@ import brainwine.gameserver.annotations.OptionalField; import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.command.CommandManager; +import brainwine.gameserver.commands.CommandManager; import brainwine.gameserver.entity.player.NotificationType; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.server.PlayerRequest; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/ConsoleRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/ConsoleRequest.java index 4ff46b9a..022e2232 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/ConsoleRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/ConsoleRequest.java @@ -1,7 +1,7 @@ package brainwine.gameserver.server.requests; import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.command.CommandManager; +import brainwine.gameserver.commands.CommandManager; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.server.PlayerRequest; diff --git a/src/main/java/brainwine/ServerThread.java b/src/main/java/brainwine/ServerThread.java index 8ca022f8..4b60a14d 100644 --- a/src/main/java/brainwine/ServerThread.java +++ b/src/main/java/brainwine/ServerThread.java @@ -8,7 +8,7 @@ import brainwine.api.Api; import brainwine.gameserver.GameServer; import brainwine.gameserver.TickLoop; -import brainwine.gameserver.command.CommandManager; +import brainwine.gameserver.commands.CommandManager; public class ServerThread extends Thread { From ab1147ba89b91c4855380263a5b0a2d12ec1fd2d Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Fri, 26 Apr 2024 23:10:25 +0200 Subject: [PATCH 045/176] Undo bfeb93be809f1b74988d0d6615f1b5c8c01e5c73 --- .../gameserver/server/requests/ZoneChangeRequest.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneChangeRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneChangeRequest.java index c87919f5..ccedb626 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneChangeRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneChangeRequest.java @@ -1,13 +1,9 @@ package brainwine.gameserver.server.requests; -import brainwine.gameserver.GameConfiguration; import brainwine.gameserver.GameServer; import brainwine.gameserver.annotations.RequestInfo; import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.Skill; import brainwine.gameserver.server.PlayerRequest; -import brainwine.gameserver.util.MapHelper; -import brainwine.gameserver.zone.Biome; import brainwine.gameserver.zone.Zone; @RequestInfo(id = 24) @@ -28,6 +24,7 @@ public void process(Player player) { } // Check survival requirement unless player has god mode enabled + /* if(!player.isGodMode()) { Biome biome = zone.getBiome(); int survival = MapHelper.getInt(GameConfiguration.getBaseConfig(), String.format("biomes.%s.survival_requirement", biome.getId())); @@ -36,7 +33,7 @@ public void process(Player player) { player.notify(String.format("Your survival skill needs to be at least level %s to enter %s worlds.", survival, biome.getId())); return; } - } + }*/ player.changeZone(zone); } From 261ec4e314c48c22e938936885af0beb16d5bbb5 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 28 Apr 2024 15:21:52 +0200 Subject: [PATCH 046/176] Minor refactors --- .../commands/admin/EntityCommand.java | 16 ++++------------ .../item/interactions/SpawnInteraction.java | 18 +++--------------- .../item/interactions/SwitchInteraction.java | 13 ++++--------- .../server/requests/BlockMineRequest.java | 7 +------ .../server/requests/BlockPlaceRequest.java | 7 +------ .../gameserver/zone/EntityManager.java | 12 ++++-------- 6 files changed, 17 insertions(+), 56 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/EntityCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/EntityCommand.java index 408f7825..701e240b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/EntityCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/EntityCommand.java @@ -5,12 +5,8 @@ import brainwine.gameserver.annotations.CommandInfo; import brainwine.gameserver.commands.Command; import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.EntityConfig; -import brainwine.gameserver.entity.EntityRegistry; -import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.entity.player.NotificationType; import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.zone.Zone; @CommandInfo(name = "entity", description = "Spawns an entity at your current location.") public class EntityCommand extends Command { @@ -23,21 +19,17 @@ public void execute(CommandExecutor executor, String[] args) { } Player player = (Player)executor; - String name = args[0]; - EntityConfig config = EntityRegistry.getEntityConfig(name); + String type = args[0]; - if(config == null) { - executor.notify(String.format("Entity with name '%s' does not exist.", name), NotificationType.SYSTEM); + if(player.getZone().spawnEntity(type, player.getBlockX(), player.getBlockY(), true) == null) { + executor.notify(String.format("Entity type '%s' does not exist.", type), NotificationType.SYSTEM); return; } - - Zone zone = player.getZone(); - zone.spawnEntity(new Npc(zone, config), (int)player.getX(), (int)player.getY(), true); } @Override public String getUsage(CommandExecutor executor) { - return "/entity "; + return "/entity "; } @Override diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnInteraction.java index e7633c7f..52e7ae55 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnInteraction.java @@ -1,9 +1,6 @@ package brainwine.gameserver.item.interactions; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.EntityConfig; -import brainwine.gameserver.entity.EntityRegistry; -import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; import brainwine.gameserver.zone.MetaBlock; @@ -22,18 +19,9 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i return; } - EntityConfig entityConfig = EntityRegistry.getEntityConfig(item.getEntitySpawns().next()); - - // Do nothing if type is invalid - if(entityConfig == null) { - return; + // Try to spawn the entity and update block mod + if(zone.spawnEntity(item.getEntitySpawns().next(), x, y) != null) { + zone.updateBlock(x, y, layer, item, 1); } - - // Spawn the entity - Npc npc = new Npc(zone, entityConfig); - zone.spawnEntity(npc, x, y); - - // Update block mod - zone.updateBlock(x, y, layer, item, 1); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java index a7575043..15c82298 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java @@ -125,21 +125,16 @@ private void switchSpawner(Zone zone, MetaBlock metaBlock) { return; } - // Determine entity type + // Try to spawn entity String entityType = MapHelper.getString((Map)config, metaBlock.getStringProperty("e")); - EntityConfig entityConfig = EntityRegistry.getEntityConfig(entityType); + Npc npc = zone.spawnEntity(entityType, metaBlock.getX(), metaBlock.getY(), true); - // Do nothing if entity config doesn't exist - if(entityConfig == null) { + // Do nothing if entity failed to spawn + if(npc == null) { return; } - // Create & spawn the entity - Npc npc = new Npc(zone, entityConfig); npc.setArtificial(true); - zone.spawnEntity(npc, metaBlock.getX(), metaBlock.getY(), true); - - // Track entity id in spawner metadata metaBlock.setProperty("eid", npc.getId()); } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index dd167a70..e5888bc8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -166,12 +166,7 @@ public void process(Player player) { // Check for entity spawns if(item.hasEntitySpawns() && block.getMod(layer) == 0 && !item.hasTimer() && !item.hasUse(ItemUseType.SPAWN)) { - EntityConfig type = EntityRegistry.getEntityConfig(item.getEntitySpawns().next()); - - if(type != null) { - Npc npc = new Npc(zone, type); - zone.spawnEntity(npc, x, y); - } + zone.spawnEntity(item.getEntitySpawns().next(), x, y); } Item inventoryItem = item.getMod() == ModType.DECAY && block.getMod(layer) > 0 ? item.getDecayInventoryItem() : item.getInventoryItem(); diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java index aec3f4f2..3abbcc3e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java @@ -258,12 +258,7 @@ private void createBlockTimer(Zone zone, Player player) { // Spawn a bunch of entities for(int i = 0; i < value; i++) { - EntityConfig entityType = EntityRegistry.getEntityConfig(item.getEntitySpawns().next()); - - if(entityType != null) { - Npc npc = new Npc(zone, entityType); - zone.spawnEntity(npc, x, y); - } + zone.spawnEntity(item.getEntitySpawns().next(), x, y); } }; break; diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java index cfddcec4..a39a0468 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java @@ -209,12 +209,10 @@ public void trySpawnBlockEntity(int x, int y) { List guardians = MapHelper.getList(metaBlock.getMetadata(), "!", Collections.emptyList()); for(String guardian : guardians) { - EntityConfig config = EntityRegistry.getEntityConfig(guardian); + Npc entity = spawnEntity(guardian, x, y); - if(config != null) { - Npc entity = new Npc(zone, config); + if(entity != null) { entity.setGuardBlock(x, y); - spawnEntity(entity, x, y); } } } @@ -230,10 +228,9 @@ public void trySpawnBlockEntity(int x, int y) { // Check for mounted entity (turrets & geysers) if(item.isEntity()) { - EntityConfig config = EntityRegistry.getEntityConfig(item.getId()); + Npc entity = spawnEntity(item.getId(), x, y); - if(config != null) { - Npc entity = new Npc(zone, config); + if(entity != null) { MetaBlock metaBlock = zone.getMetaBlock(x, y); // Set owner entity if it has one @@ -242,7 +239,6 @@ public void trySpawnBlockEntity(int x, int y) { } entity.setMountBlock(x, y); - spawnEntity(entity, x, y); mountedNpcs.put(index, entity); } } From 1eda98539c6f65565d00aacbd5b186a836e47f1d Mon Sep 17 00:00:00 2001 From: Kuroppoi Date: Wed, 1 May 2024 16:16:17 +0200 Subject: [PATCH 047/176] Custom build logic (#49) --- .gitattributes | 2 + .github/workflows/build.yml | 2 +- .gitignore | 3 +- README.md | 74 +++++----- api/build.gradle | 6 +- build-logic/build.gradle | 28 ++++ .../java/brainwine/bootstrap/Bootstrap.java | 84 ++++++++++++ .../java/brainwine/bootstrap/Constants.java | 12 ++ .../brainwine/build/DistributionPlugin.java | 14 ++ .../brainwine/build/DistributionTask.java | 128 ++++++++++++++++++ build.gradle | 28 ++-- gameserver/build.gradle | 6 +- settings.gradle | 4 + shared/build.gradle | 4 - .../brainwine/{Bootstrap.java => Main.java} | 6 +- src/main/java/brainwine/ServerThread.java | 10 +- src/main/java/brainwine/gui/MainView.java | 8 +- src/main/java/brainwine/gui/ServerPanel.java | 20 +-- 18 files changed, 342 insertions(+), 97 deletions(-) create mode 100644 build-logic/build.gradle create mode 100644 build-logic/src/boot/java/brainwine/bootstrap/Bootstrap.java create mode 100644 build-logic/src/boot/java/brainwine/bootstrap/Constants.java create mode 100644 build-logic/src/main/java/brainwine/build/DistributionPlugin.java create mode 100644 build-logic/src/main/java/brainwine/build/DistributionTask.java rename src/main/java/brainwine/{Bootstrap.java => Main.java} (98%) diff --git a/.gitattributes b/.gitattributes index f7fa1f5e..5064e201 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,5 @@ +/gradlew text eol=lf + # These are explicitly windows files and should use crlf *.bat text eol=crlf diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b3c24f1e..774dc16c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,5 +32,5 @@ jobs: uses: actions/upload-artifact@v3.1.1 with: name: brainwine - path: build/libs/brainwine.jar + path: build/dist/brainwine.jar retention-days: 7 diff --git a/.gitignore b/.gitignore index 3e0742f1..80177931 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Gradle .gradle build +!**/src/**/build # Eclipse *.launch @@ -11,4 +12,4 @@ build bin # Misc -run \ No newline at end of file +run diff --git a/README.md b/README.md index b57da347..7250b78e 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,49 @@ -# Brainwine -[![build](https://github.com/kuroppoi/brainwine/actions/workflows/build.yml/badge.svg)](https://github.com/kuroppoi/brainwine/actions) - -Brainwine is a Deepworld private server written in Java, made with user-friendliness and portability in mind. -Due to the time it will take for this project to be complete (and my inconsistent working on it), brainwine has been prematurely open-sourced -and is free for all to use.\ -Keep in mind, though, that this server is not finished yet. Expect to encounter bad code, bugs and missing features!\ -Brainwine is currently compatible with the following versions of Deepworld: -- Steam: `v3.13.1` -- iOS: `v2.11.0.1` -- MacOS: `v2.11.1` +

Brainwine

+

+ build + release +

-## Features -A list of all planned, in-progress and finished features can be found [here.](https://github.com/kuroppoi/brainwine/projects/1) +Brainwine is a Deepworld private server written in Java, designed to be portable and easy to use.\ +It's still a work in progress, so keep in mind that it's not yet feature-complete. (A to-do list can be found [here](https://github.com/kuroppoi/brainwine/projects/1).)\ +Brainwine currently supports the following versions of Deepworld: -## Setup +- Windows: `v3.13.1` +- iOS: `v2.11.0.1` +- MacOS: `v2.11.1` -### Setting up the client +## Quick Local Setup -Before you can connect to a server, a few modifications need to be made to the Deepworld game client.\ -The exact process of this differs per platform.\ -You may download an installation package for your desired platform [here.](https://github.com/kuroppoi/brainwine/releases/tag/patching-kits-1.0) +- Install [Java 8](https://adoptium.net/temurin/releases/?package=jdk&version=8). +- Download the [latest Brainwine release](https://github.com/kuroppoi/brainwine/releases/latest). +- Run Brainwine, go to the server tab and start the server. +- Go to the game tab and start the game. + - If this isn't available for you, download a [patching kit](https://github.com/kuroppoi/brainwine/releases/tag/patching-kits-1.0) for your platform and follow the instructions there. +- Register a new account and play the game. -### Setting up the server +## Building #### Prerequisites -- Java 8 or newer - -You can download the latest release [here.](https://github.com/kuroppoi/brainwine/releases/latest)\ -Alternatively, if you wish to build from source, clone this repository with the `--recurse-submodules` flag\ -and run `gradlew dist` in the root directory of the repository.\ -After the build has finished, the output jar will be located in `build/libs`.\ -You may then start the server through the gui, or start it directly by running the jar with the `disablegui` flag. +- Java 8 Development Kit -#### Configurations +```sh +git clone --recurse-submodules https://github.com/kuroppoi/brainwine.git +cd brainwine +./gradlew dist +``` -On first-time startup, configuration files will be generated which you may modify however you like: -- `api.json` Configuration file for news & API connectivity information. -- `loottables.json` Configuration file for which loot may be obtained from containers. -- `spawning.json` Configuration file for entity spawns per biome. -- `generators` Folder containing configuration files for zone generators. +The output will be located in the `/build/dist` directory. -## Contributions +## Usage -Disagree with how I did something? Found a potential error? See some room for improvement? Or just want to add a feature? -Glad to hear it! Feel free to make a pull request anytime. Just make sure you follow the code style! -And, apologies in advance for the lack of documentation. Haven't gotten around to do it yet. Sorry! +Execute `brainwine.jar` to start the program. Navigate to the server tab and press the button to start the server.\ +It is also possible to start the server immediately with no user interface: -## Issues +```sh +# This behavior is the default on platforms that do not support Java's Desktop API. +java -jar brainwine.jar disablegui +``` -Found a bug? Before posting an issue, make sure your build is up-to-date and your issue has not already been posted before. -Provide a detailed explanation of the issue, and how to reproduce it. I'll get to it ASAP! +To connect to a local or remote server, download a [patching kit](https://github.com/kuroppoi/brainwine/releases/tag/patching-kits-1.0) for your desired platform.\ +Alternatively, Windows users may use the program's user interface to configure the host settings and start the game. diff --git a/api/build.gradle b/api/build.gradle index f441adea..84dcbf45 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -8,9 +8,5 @@ repositories { dependencies { implementation 'io.javalin:javalin:4.6.8' - implementation project(':shared') -} - -jar { - archiveBaseName = 'brainwine-api' + implementation project(':brainwine-shared') } diff --git a/build-logic/build.gradle b/build-logic/build.gradle new file mode 100644 index 00000000..513b583f --- /dev/null +++ b/build-logic/build.gradle @@ -0,0 +1,28 @@ +plugins { + id 'java-gradle-plugin' +} + +sourceSets { + boot { + + } + main { + compileClasspath += sourceSets.boot.output + runtimeClasspath += sourceSets.boot.output + } +} + +gradlePlugin { + plugins { + distributionPlugin { + id = "brainwine.distribution" + implementationClass = "brainwine.build.DistributionPlugin" + } + } +} + +jar { + from sourceSets.boot.output +} + +version = '1.0.0-SNAPSHOT' diff --git a/build-logic/src/boot/java/brainwine/bootstrap/Bootstrap.java b/build-logic/src/boot/java/brainwine/bootstrap/Bootstrap.java new file mode 100644 index 00000000..e2553f4a --- /dev/null +++ b/build-logic/src/boot/java/brainwine/bootstrap/Bootstrap.java @@ -0,0 +1,84 @@ +package brainwine.bootstrap; + +import static brainwine.bootstrap.Constants.BOOT_CLASS_KEY; +import static brainwine.bootstrap.Constants.CLASS_PATH_KEY; +import static brainwine.bootstrap.Constants.LIBRARY_PATH; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.util.Enumeration; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +public class Bootstrap { + + public static void main(String[] args) { + new Bootstrap().run(args); + } + + private void run(String[] args) { + Attributes attributes = null; + + try { + Enumeration resources = getClass().getClassLoader().getResources(JarFile.MANIFEST_NAME); + + while(resources.hasMoreElements()) { + try(InputStream inputStream = resources.nextElement().openStream()) { + Manifest manifest = new Manifest(inputStream); + if(getClass().getName().equals(manifest.getMainAttributes().getValue(Attributes.Name.MAIN_CLASS))) { + attributes = manifest.getMainAttributes(); + break; + } + } + } + } catch(IOException e) { + System.err.println("Could not load manifest file"); + e.printStackTrace(); + System.exit(-1); + } + + String[] libraryNames = attributes.getValue(CLASS_PATH_KEY).split(";"); + URL[] libraryUrls = new URL[libraryNames.length]; + + try { + for(int i = 0; i < libraryNames.length; i++) { + String libraryName = libraryNames[i]; + + try(InputStream inputStream = getClass().getResourceAsStream(String.format("/%s/%s", LIBRARY_PATH, libraryName))) { + File outputFile = new File("libraries", libraryName); + libraryUrls[i] = outputFile.toURI().toURL(); + + if(outputFile.exists()) { + continue; + } + + outputFile.getParentFile().mkdirs(); + Files.copy(inputStream, outputFile.toPath()); + } + } + } catch(Exception e) { + System.err.println("Could not extract library JARs"); + e.printStackTrace(); + System.exit(-1); + } + + URLClassLoader classLoader = new URLClassLoader(libraryUrls, getClass().getClassLoader().getParent()); + Thread.currentThread().setContextClassLoader(classLoader); + + try { + Class mainClass = Class.forName(attributes.getValue(BOOT_CLASS_KEY), true, classLoader); + Method method = mainClass.getMethod("main", String[].class); + method.invoke(null, (Object)args); + } catch(ReflectiveOperationException e) { + System.err.println("Could not invoke entry point"); + e.printStackTrace(); + System.exit(-1); + } + } +} diff --git a/build-logic/src/boot/java/brainwine/bootstrap/Constants.java b/build-logic/src/boot/java/brainwine/bootstrap/Constants.java new file mode 100644 index 00000000..560c1957 --- /dev/null +++ b/build-logic/src/boot/java/brainwine/bootstrap/Constants.java @@ -0,0 +1,12 @@ +package brainwine.bootstrap; + +import java.util.jar.Attributes; + +public class Constants { + + public static final Attributes.Name BOOT_CLASS_KEY = new Attributes.Name("Dist-Boot-Class"); + public static final Attributes.Name CLASS_PATH_KEY = new Attributes.Name("Dist-Class-Path"); + public static final String LICENSE_PATH = "META-INF/LICENSE"; + public static final String LIBRARY_PATH = "META-INF/libraries"; + public static final Class MAIN_CLASS = Bootstrap.class; +} diff --git a/build-logic/src/main/java/brainwine/build/DistributionPlugin.java b/build-logic/src/main/java/brainwine/build/DistributionPlugin.java new file mode 100644 index 00000000..c1580d92 --- /dev/null +++ b/build-logic/src/main/java/brainwine/build/DistributionPlugin.java @@ -0,0 +1,14 @@ +package brainwine.build; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlugin; + +public class DistributionPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getPlugins().apply(JavaPlugin.class); + project.getTasks().register("dist", DistributionTask.class, task -> task.dependsOn("build")); + } +} diff --git a/build-logic/src/main/java/brainwine/build/DistributionTask.java b/build-logic/src/main/java/brainwine/build/DistributionTask.java new file mode 100644 index 00000000..28513268 --- /dev/null +++ b/build-logic/src/main/java/brainwine/build/DistributionTask.java @@ -0,0 +1,128 @@ +package brainwine.build; + +import static brainwine.bootstrap.Constants.BOOT_CLASS_KEY; +import static brainwine.bootstrap.Constants.CLASS_PATH_KEY; +import static brainwine.bootstrap.Constants.LIBRARY_PATH; +import static brainwine.bootstrap.Constants.LICENSE_PATH; +import static brainwine.bootstrap.Constants.MAIN_CLASS; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.file.FileTree; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.initialization.IncludedBuild; +import org.gradle.api.invocation.Gradle; +import org.gradle.api.provider.Property; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.bundling.Jar; + +public abstract class DistributionTask extends DefaultTask { + + private final FileTree bootCodeTree; + + @Input + public abstract Property getMainClass(); + + @Input + @Optional + public abstract Property getArchiveFileName(); + + @Input + @Optional + public abstract RegularFileProperty getLicenseFile(); + + @Inject + public DistributionTask(Gradle gradle) { + IncludedBuild build = gradle.getIncludedBuilds().stream().filter(x -> x.getName().equals("build-logic")).findFirst().get(); + bootCodeTree = getProject().fileTree(new File(build.getProjectDir(), "build/classes/java/boot")); + } + + @TaskAction + public void createDistributionArchive() throws IOException { + Configuration config = getProject().getConfigurations().getByName("runtimeClasspath"); + Jar jarTask = (Jar)getProject().getTasks().getByName("jar"); + String archiveFileName = getArchiveFileName().getOrElse(jarTask.getArchiveFileName().get()); + File outputDirectory = new File(getProject().getBuildDir(), "dist"); + outputDirectory.mkdirs(); + File outputFile = new File(outputDirectory, archiveFileName); + + // Fetch libraries + List classpath = new ArrayList<>(); + config.getResolvedConfiguration().getResolvedArtifacts().forEach(artifact -> classpath.add(artifact.getFile())); + jarTask.getOutputs().getFiles().forEach(classpath::add); + classpath.sort((a, b) -> a.getName().compareTo(b.getName())); // Guarantee file order + + // Create jar manifest + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, MAIN_CLASS.getName()); + manifest.getMainAttributes().put(BOOT_CLASS_KEY, getMainClass().get()); + manifest.getMainAttributes().put(CLASS_PATH_KEY, String.join(";", classpath.stream().map(File::getName).collect(Collectors.toList()))); + manifest.getMainAttributes().putValue("Multi-Release", "true"); + + // Create jar file + try(JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(outputFile))) { + // Add manifest + addJarManifest(outputStream, manifest); + + // Add libraries + for(File file : classpath) { + addFileToJar(outputStream, file, String.format("%s/%s", LIBRARY_PATH, file.getName())); + } + + // Add boot code + bootCodeTree.visit(details -> { + if(!details.isDirectory()) { + try { + addFileToJar(outputStream, details.getFile(), details.getPath()); + } catch(IOException e) { + throw new GradleException(e.getMessage(), e); + } + } + }); + + // Add license + RegularFileProperty licenseFile = getLicenseFile(); + + if(licenseFile.isPresent()) { + addFileToJar(outputStream, licenseFile.get().getAsFile(), LICENSE_PATH); + } + } + } + + private void addJarManifest(JarOutputStream outputStream, Manifest manifest) throws IOException { + JarEntry entry = new JarEntry(JarFile.MANIFEST_NAME); + entry.setTime(0); + outputStream.putNextEntry(entry); + manifest.write(outputStream); + outputStream.closeEntry(); + } + + private void addFileToJar(JarOutputStream outputStream, File file, String targetPath) throws IOException { + byte[] bytes = Files.readAllBytes(file.toPath()); + JarEntry entry = new JarEntry(targetPath); + entry.setTime(0); + entry.setSize(bytes.length); + outputStream.putNextEntry(entry); + outputStream.write(bytes); + outputStream.closeEntry(); + } +} diff --git a/build.gradle b/build.gradle index fcf038c0..c47d5847 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,9 @@ plugins { - id 'java' + id 'brainwine.distribution' } ext { - mainClass = 'brainwine.Bootstrap' + mainClass = 'brainwine.Main' workingDirectory = 'run' } @@ -15,26 +15,14 @@ dependencies { implementation 'com.formdev:flatlaf-intellij-themes:3.0' implementation 'com.formdev:flatlaf-extras:3.0' implementation 'com.formdev:flatlaf:3.0' - implementation project(':api') - implementation project(':gameserver') - implementation project(':shared') + implementation project(':brainwine-api') + implementation project(':brainwine-gameserver') + implementation project(':brainwine-shared') } -task dist(type: Jar) { - manifest { - attributes 'Multi-Release': 'true', - 'Main-Class': project.ext.mainClass - } - - from { - configurations.runtimeClasspath.collect { - it.isDirectory() ? it : zipTree(it) - } - } - - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - dependsOn configurations.runtimeClasspath - with jar +dist { + mainClass = project.ext.mainClass + licenseFile = file("${project.rootDir}/LICENSE.md") } task run(type: JavaExec) { diff --git a/gameserver/build.gradle b/gameserver/build.gradle index 847e487e..b707887c 100644 --- a/gameserver/build.gradle +++ b/gameserver/build.gradle @@ -24,11 +24,7 @@ dependencies { implementation 'org.reflections:reflections:0.10.2' implementation 'io.netty:netty-all:4.1.79.Final' implementation 'org.mindrot:jbcrypt:0.4' - implementation project(':shared') -} - -jar { - archiveBaseName = 'brainwine-gameserver' + implementation project(':brainwine-shared') } processResources.includeEmptyDirs = false diff --git a/settings.gradle b/settings.gradle index 38583d44..566f3e28 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,6 @@ rootProject.name = 'brainwine' +includeBuild 'build-logic' include('api', 'gameserver', 'shared') +project(":api").name = 'brainwine-api' +project(':gameserver').name = 'brainwine-gameserver' +project(':shared').name = 'brainwine-shared' diff --git a/shared/build.gradle b/shared/build.gradle index 451764c6..0782595a 100644 --- a/shared/build.gradle +++ b/shared/build.gradle @@ -15,7 +15,3 @@ dependencies { api 'commons-validator:commons-validator:1.7' api 'org.apache.commons:commons-text:1.9' } - -jar { - archiveBaseName = 'brainwine-shared' -} diff --git a/src/main/java/brainwine/Bootstrap.java b/src/main/java/brainwine/Main.java similarity index 98% rename from src/main/java/brainwine/Bootstrap.java rename to src/main/java/brainwine/Main.java index bb1969fa..9f09f3f9 100644 --- a/src/main/java/brainwine/Bootstrap.java +++ b/src/main/java/brainwine/Main.java @@ -21,7 +21,7 @@ import brainwine.gui.theme.ThemeManager; import brainwine.util.SwingUtils; -public class Bootstrap { +public class Main { private static Logger logger = LogManager.getLogger(); private static boolean disableGui = false; @@ -41,10 +41,10 @@ public static void main(String[] args) { } } - new Bootstrap(); + new Main(); } - public Bootstrap() { + public Main() { // Create gui or directly start server if gui is disabled or not supported if(!disableGui && (Desktop.isDesktopSupported() || forceGui)) { try { diff --git a/src/main/java/brainwine/ServerThread.java b/src/main/java/brainwine/ServerThread.java index 4b60a14d..47994ed5 100644 --- a/src/main/java/brainwine/ServerThread.java +++ b/src/main/java/brainwine/ServerThread.java @@ -13,14 +13,14 @@ public class ServerThread extends Thread { private static Logger logger = LogManager.getLogger(); - private final Bootstrap bootstrap; + private final Main main; private GameServer gameServer; private Api api; private boolean running; - public ServerThread(Bootstrap bootstrap) { + public ServerThread(Main main) { super("server"); - this.bootstrap = bootstrap; + this.main = main; } @Override @@ -36,7 +36,7 @@ public void run() { logger.info(SERVER_MARKER, "Server has started"); running = true; - bootstrap.onServerStarted(); + main.onServerStarted(); while(!gameServer.shouldStop()) { tickLoop.update(); @@ -80,7 +80,7 @@ private void stopUnsafe() { logger.error(SERVER_MARKER, "An unexpected exception occured whilst shutting down", e); } finally { running = false; - bootstrap.onServerStopped(); + main.onServerStopped(); } } diff --git a/src/main/java/brainwine/gui/MainView.java b/src/main/java/brainwine/gui/MainView.java index 1af4c3d0..13ac9047 100644 --- a/src/main/java/brainwine/gui/MainView.java +++ b/src/main/java/brainwine/gui/MainView.java @@ -25,7 +25,7 @@ import com.formdev.flatlaf.extras.components.FlatTabbedPane; import com.formdev.flatlaf.extras.components.FlatTabbedPane.TabAlignment; -import brainwine.Bootstrap; +import brainwine.Main; import brainwine.util.DesktopUtils; import brainwine.util.OperatingSystem; import brainwine.util.ProcessResult; @@ -43,7 +43,7 @@ public class MainView { private final ServerPanel serverPanel; private final SettingsPanel settingsPanel; - public MainView(Bootstrap bootstrap) { + public MainView(Main main) { logger.info(GUI_MARKER, "Creating main view ..."); // Panel @@ -59,7 +59,7 @@ public MainView(Bootstrap bootstrap) { tabbedPane.addTab("Play Game", UIManager.getIcon("Brainwine.playIcon"), new GamePanel(this)); } - tabbedPane.addTab("Server", UIManager.getIcon("Brainwine.serverIcon"), serverPanel = new ServerPanel(bootstrap)); + tabbedPane.addTab("Server", UIManager.getIcon("Brainwine.serverIcon"), serverPanel = new ServerPanel(main)); tabbedPane.addTab("Settings", UIManager.getIcon("Brainwine.settingsIcon"), settingsPanel = new SettingsPanel(this)); panel.add(tabbedPane); @@ -79,7 +79,7 @@ public MainView(Bootstrap bootstrap) { frame.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent event) { - bootstrap.closeApplication(); + main.closeApplication(); } }); frame.setJMenuBar(menuBar); diff --git a/src/main/java/brainwine/gui/ServerPanel.java b/src/main/java/brainwine/gui/ServerPanel.java index 50a48df4..efc1e7de 100644 --- a/src/main/java/brainwine/gui/ServerPanel.java +++ b/src/main/java/brainwine/gui/ServerPanel.java @@ -27,20 +27,20 @@ import com.formdev.flatlaf.extras.components.FlatScrollPane; import com.formdev.flatlaf.extras.components.FlatTextField; -import brainwine.Bootstrap; +import brainwine.Main; import brainwine.ListenableAppender; import brainwine.gui.event.AutoScrollAdjustmentListener; @SuppressWarnings("serial") public class ServerPanel extends JPanel { - private final Bootstrap bootstrap; + private final Main main; private final JTextPane consoleOutput; private final FlatTextField consoleInput; private final JButton serverButton; - public ServerPanel(Bootstrap bootstrap) { - this.bootstrap = bootstrap; + public ServerPanel(Main main) { + this.main = main; setLayout(new BorderLayout()); setBorder(BorderFactory.createEmptyBorder(0, 3, 3, 3)); @@ -114,32 +114,32 @@ private void processConsoleInput() { if(!commandLine.isEmpty()) { appendConsoleOutput(String.format("> %s\n", commandLine), Color.GRAY); - bootstrap.executeCommand(commandLine); + main.executeCommand(commandLine); consoleInput.setText(null); } } private void toggleServer() { - if(bootstrap.isServerRunning()) { + if(main.isServerRunning()) { serverButton.setEnabled(false); consoleInput.setEditable(false); consoleInput.setText(null); - bootstrap.stopServer(); + main.stopServer(); } else { serverButton.setEnabled(false); consoleInput.setEditable(true); consoleOutput.setText(null); - bootstrap.startServer(); + main.startServer(); } } public void enableServerButton() { - if(!bootstrap.isServerRunning()) { + if(!main.isServerRunning()) { consoleInput.setEditable(false); consoleInput.setText(null); } - serverButton.setText(bootstrap.isServerRunning() ? "Stop Server" : "Start Server"); + serverButton.setText(main.isServerRunning() ? "Stop Server" : "Start Server"); serverButton.setEnabled(true); } } From d4e84ad0fd61cf68b36f24be6fcae1f11e364ba3 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Wed, 1 May 2024 16:46:04 +0200 Subject: [PATCH 048/176] Remove unused imports --- .../gameserver/item/interactions/SwitchInteraction.java | 2 -- .../brainwine/gameserver/server/requests/BlockMineRequest.java | 3 --- .../gameserver/server/requests/BlockPlaceRequest.java | 1 - 3 files changed, 6 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java index 15c82298..8096fd13 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java @@ -8,8 +8,6 @@ import org.apache.commons.text.WordUtils; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.EntityConfig; -import brainwine.gameserver.entity.EntityRegistry; import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.DamageType; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index e5888bc8..fbd51cbb 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -6,9 +6,6 @@ import brainwine.gameserver.annotations.RequestInfo; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.EntityConfig; -import brainwine.gameserver.entity.EntityRegistry; -import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.entity.player.NotificationType; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.entity.player.Skill; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java index 3abbcc3e..ae139941 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java @@ -4,7 +4,6 @@ import brainwine.gameserver.annotations.RequestInfo; import brainwine.gameserver.entity.EntityConfig; -import brainwine.gameserver.entity.EntityRegistry; import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.entity.player.Skill; From f0c1187891801595ebf9d82dfd81aa410ae25485 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 2 May 2024 16:58:23 +0200 Subject: [PATCH 049/176] Improve build logic --- .../java/brainwine/bootstrap/Bootstrap.java | 36 ++---- .../java/brainwine/bootstrap/Constants.java | 8 +- .../brainwine/bootstrap/DirectoryIndex.java | 114 ++++++++++++++++++ .../boot/java/brainwine/bootstrap/SHA256.java | 28 +++++ .../brainwine/build/DistributionTask.java | 77 +++++------- .../main/java/brainwine/build/JarBundler.java | 60 +++++++++ .../brainwine/build/OutputStreamWriter.java | 10 ++ 7 files changed, 254 insertions(+), 79 deletions(-) create mode 100644 build-logic/src/boot/java/brainwine/bootstrap/DirectoryIndex.java create mode 100644 build-logic/src/boot/java/brainwine/bootstrap/SHA256.java create mode 100644 build-logic/src/main/java/brainwine/build/JarBundler.java create mode 100644 build-logic/src/main/java/brainwine/build/OutputStreamWriter.java diff --git a/build-logic/src/boot/java/brainwine/bootstrap/Bootstrap.java b/build-logic/src/boot/java/brainwine/bootstrap/Bootstrap.java index e2553f4a..f621e188 100644 --- a/build-logic/src/boot/java/brainwine/bootstrap/Bootstrap.java +++ b/build-logic/src/boot/java/brainwine/bootstrap/Bootstrap.java @@ -1,8 +1,7 @@ package brainwine.bootstrap; import static brainwine.bootstrap.Constants.BOOT_CLASS_KEY; -import static brainwine.bootstrap.Constants.CLASS_PATH_KEY; -import static brainwine.bootstrap.Constants.LIBRARY_PATH; +import static brainwine.bootstrap.Constants.JAR_LIBRARY_PATH; import java.io.File; import java.io.IOException; @@ -10,7 +9,6 @@ import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; -import java.nio.file.Files; import java.util.Enumeration; import java.util.jar.Attributes; import java.util.jar.JarFile; @@ -23,45 +21,31 @@ public static void main(String[] args) { } private void run(String[] args) { - Attributes attributes = null; + String mainClassName = null; try { Enumeration resources = getClass().getClassLoader().getResources(JarFile.MANIFEST_NAME); while(resources.hasMoreElements()) { try(InputStream inputStream = resources.nextElement().openStream()) { - Manifest manifest = new Manifest(inputStream); + Manifest manifest = new Manifest(inputStream); + if(getClass().getName().equals(manifest.getMainAttributes().getValue(Attributes.Name.MAIN_CLASS))) { - attributes = manifest.getMainAttributes(); + mainClassName = manifest.getMainAttributes().getValue(BOOT_CLASS_KEY); break; } } } } catch(IOException e) { - System.err.println("Could not load manifest file"); + System.err.println("Could not get main class name"); e.printStackTrace(); System.exit(-1); } - String[] libraryNames = attributes.getValue(CLASS_PATH_KEY).split(";"); - URL[] libraryUrls = new URL[libraryNames.length]; + URL[] libraryUrls = null; - try { - for(int i = 0; i < libraryNames.length; i++) { - String libraryName = libraryNames[i]; - - try(InputStream inputStream = getClass().getResourceAsStream(String.format("/%s/%s", LIBRARY_PATH, libraryName))) { - File outputFile = new File("libraries", libraryName); - libraryUrls[i] = outputFile.toURI().toURL(); - - if(outputFile.exists()) { - continue; - } - - outputFile.getParentFile().mkdirs(); - Files.copy(inputStream, outputFile.toPath()); - } - } + try { + libraryUrls = DirectoryIndex.extractDirectory(JAR_LIBRARY_PATH, new File("libraries")); } catch(Exception e) { System.err.println("Could not extract library JARs"); e.printStackTrace(); @@ -72,7 +56,7 @@ private void run(String[] args) { Thread.currentThread().setContextClassLoader(classLoader); try { - Class mainClass = Class.forName(attributes.getValue(BOOT_CLASS_KEY), true, classLoader); + Class mainClass = Class.forName(mainClassName, true, classLoader); Method method = mainClass.getMethod("main", String[].class); method.invoke(null, (Object)args); } catch(ReflectiveOperationException e) { diff --git a/build-logic/src/boot/java/brainwine/bootstrap/Constants.java b/build-logic/src/boot/java/brainwine/bootstrap/Constants.java index 560c1957..88d76f5c 100644 --- a/build-logic/src/boot/java/brainwine/bootstrap/Constants.java +++ b/build-logic/src/boot/java/brainwine/bootstrap/Constants.java @@ -4,9 +4,9 @@ public class Constants { - public static final Attributes.Name BOOT_CLASS_KEY = new Attributes.Name("Dist-Boot-Class"); - public static final Attributes.Name CLASS_PATH_KEY = new Attributes.Name("Dist-Class-Path"); - public static final String LICENSE_PATH = "META-INF/LICENSE"; - public static final String LIBRARY_PATH = "META-INF/libraries"; + public static final Attributes.Name BOOT_CLASS_KEY = new Attributes.Name("Boot-Class"); + public static final String DIRECTORY_INDEX_FILE = "index"; + public static final String JAR_LICENSE_PATH = "META-INF/LICENSE"; + public static final String JAR_LIBRARY_PATH = "META-INF/libraries"; public static final Class MAIN_CLASS = Bootstrap.class; } diff --git a/build-logic/src/boot/java/brainwine/bootstrap/DirectoryIndex.java b/build-logic/src/boot/java/brainwine/bootstrap/DirectoryIndex.java new file mode 100644 index 00000000..5bb08c8b --- /dev/null +++ b/build-logic/src/boot/java/brainwine/bootstrap/DirectoryIndex.java @@ -0,0 +1,114 @@ +package brainwine.bootstrap; + +import static brainwine.bootstrap.Constants.DIRECTORY_INDEX_FILE; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class DirectoryIndex { + + private final Map map = new HashMap<>(); + + @Override + public int hashCode() { + return map.hashCode(); + } + + @Override + public boolean equals(Object object) { + return map.equals(object); + } + + public static URL[] extractDirectory(String path, File outputDirectory) throws IOException { + DirectoryIndex index = new DirectoryIndex(); + outputDirectory.mkdirs(); + + // Read index file + try(InputStream inputStream = DirectoryIndex.class.getResourceAsStream(String.format("/%s/%s", path, DIRECTORY_INDEX_FILE))) { + index.read(inputStream); + } + + URL[] urls = new URL[index.size()]; + int loopIndex = 0; + + // Extract files + for(String name : index.getNames()) { + try(InputStream inputStream = DirectoryIndex.class.getResourceAsStream(String.format("/%s/%s", path, name))) { + File outputFile = new File(outputDirectory, name); + urls[loopIndex++] = outputFile.toURI().toURL(); + + // Skip file if it already exists and the hashes match + if(outputFile.exists() && index.getHash(name).equals(SHA256.hash(outputFile))) { + continue; + } + + Files.copy(inputStream, outputFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + } + + return urls; + } + + public void read(InputStream inputStream) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + reader.lines().forEach(line -> { + String[] segments = line.split("\t"); + + if(segments.length == 2) { + map.put(segments[0], segments[1]); + } + }); + } + + public void write(OutputStream outputStream) throws IOException { + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream)); + + for(String name : map.keySet()) { + writer.write(String.format("%s\t%s\n", name, map.get(name))); + } + + writer.flush(); + } + + public void put(String name, String hash) { + map.put(name, hash); + } + + public String remove(String name) { + return map.remove(name); + } + + public String getHash(String name) { + return map.get(name); + } + + public Set getNames() { + return map.keySet(); + } + + public Collection getHashes() { + return map.values(); + } + + public Set> getEntries() { + return map.entrySet(); + } + + public int size() { + return map.size(); + } +} diff --git a/build-logic/src/boot/java/brainwine/bootstrap/SHA256.java b/build-logic/src/boot/java/brainwine/bootstrap/SHA256.java new file mode 100644 index 00000000..1e1df74c --- /dev/null +++ b/build-logic/src/boot/java/brainwine/bootstrap/SHA256.java @@ -0,0 +1,28 @@ +package brainwine.bootstrap; + +import java.io.File; +import java.io.IOException; +import java.math.BigInteger; +import java.nio.file.Files; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class SHA256 { + + public static String hash(File file) throws IOException { + return hash(Files.readAllBytes(file.toPath())); + } + + public static String hash(byte[] bytes) { + MessageDigest digest; + + try { + digest = MessageDigest.getInstance("SHA-256"); + } catch(NoSuchAlgorithmException e) { + throw new RuntimeException(e); // This should never happen + } + + byte[] hash = digest.digest(bytes); + return new BigInteger(1, hash).toString(16).toUpperCase(); + } +} diff --git a/build-logic/src/main/java/brainwine/build/DistributionTask.java b/build-logic/src/main/java/brainwine/build/DistributionTask.java index 28513268..f53f0f66 100644 --- a/build-logic/src/main/java/brainwine/build/DistributionTask.java +++ b/build-logic/src/main/java/brainwine/build/DistributionTask.java @@ -1,23 +1,17 @@ package brainwine.build; import static brainwine.bootstrap.Constants.BOOT_CLASS_KEY; -import static brainwine.bootstrap.Constants.CLASS_PATH_KEY; -import static brainwine.bootstrap.Constants.LIBRARY_PATH; -import static brainwine.bootstrap.Constants.LICENSE_PATH; +import static brainwine.bootstrap.Constants.JAR_LIBRARY_PATH; +import static brainwine.bootstrap.Constants.JAR_LICENSE_PATH; import static brainwine.bootstrap.Constants.MAIN_CLASS; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.nio.file.Files; import java.util.ArrayList; import java.util.List; import java.util.jar.Attributes; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.JarOutputStream; import java.util.jar.Manifest; -import java.util.stream.Collectors; import javax.inject.Inject; @@ -25,18 +19,20 @@ import org.gradle.api.GradleException; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.FileTree; -import org.gradle.api.file.RegularFileProperty; import org.gradle.api.initialization.IncludedBuild; import org.gradle.api.invocation.Gradle; import org.gradle.api.provider.Property; import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.bundling.Jar; public abstract class DistributionTask extends DefaultTask { private final FileTree bootCodeTree; + private File outputDirectory; @Input public abstract Property getMainClass(); @@ -45,14 +41,15 @@ public abstract class DistributionTask extends DefaultTask { @Optional public abstract Property getArchiveFileName(); - @Input + @InputFile @Optional - public abstract RegularFileProperty getLicenseFile(); + public abstract Property getLicenseFile(); @Inject public DistributionTask(Gradle gradle) { IncludedBuild build = gradle.getIncludedBuilds().stream().filter(x -> x.getName().equals("build-logic")).findFirst().get(); bootCodeTree = getProject().fileTree(new File(build.getProjectDir(), "build/classes/java/boot")); + outputDirectory = new File(getProject().getBuildDir(), "dist"); } @TaskAction @@ -65,64 +62,46 @@ public void createDistributionArchive() throws IOException { File outputFile = new File(outputDirectory, archiveFileName); // Fetch libraries - List classpath = new ArrayList<>(); - config.getResolvedConfiguration().getResolvedArtifacts().forEach(artifact -> classpath.add(artifact.getFile())); - jarTask.getOutputs().getFiles().forEach(classpath::add); - classpath.sort((a, b) -> a.getName().compareTo(b.getName())); // Guarantee file order - + List libraryFiles = new ArrayList<>(); + config.getResolvedConfiguration().getResolvedArtifacts().forEach(artifact -> libraryFiles.add(artifact.getFile())); + jarTask.getOutputs().getFiles().forEach(libraryFiles::add); + // Create jar manifest Manifest manifest = new Manifest(); manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, MAIN_CLASS.getName()); manifest.getMainAttributes().put(BOOT_CLASS_KEY, getMainClass().get()); - manifest.getMainAttributes().put(CLASS_PATH_KEY, String.join(";", classpath.stream().map(File::getName).collect(Collectors.toList()))); manifest.getMainAttributes().putValue("Multi-Release", "true"); // Create jar file - try(JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(outputFile))) { - // Add manifest - addJarManifest(outputStream, manifest); - - // Add libraries - for(File file : classpath) { - addFileToJar(outputStream, file, String.format("%s/%s", LIBRARY_PATH, file.getName())); - } - - // Add boot code + try(JarBundler bundler = new JarBundler(new FileOutputStream(outputFile), manifest)) { + // Add boot code bootCodeTree.visit(details -> { if(!details.isDirectory()) { try { - addFileToJar(outputStream, details.getFile(), details.getPath()); - } catch(IOException e) { - throw new GradleException(e.getMessage(), e); - } + bundler.addFile(details.getFile(), details.getPath()); + } catch(IOException e) { + throw new GradleException(e.getMessage(), e); + } } }); - // Add license - RegularFileProperty licenseFile = getLicenseFile(); + // Add libraries + bundler.embedDirectory(libraryFiles, JAR_LIBRARY_PATH); - if(licenseFile.isPresent()) { - addFileToJar(outputStream, licenseFile.get().getAsFile(), LICENSE_PATH); + // Add license + if(getLicenseFile().isPresent()) { + bundler.addFile(getLicenseFile().get(), JAR_LICENSE_PATH); } } } - private void addJarManifest(JarOutputStream outputStream, Manifest manifest) throws IOException { - JarEntry entry = new JarEntry(JarFile.MANIFEST_NAME); - entry.setTime(0); - outputStream.putNextEntry(entry); - manifest.write(outputStream); - outputStream.closeEntry(); + public void setOutputDirectory(File outputDirectory) { + this.outputDirectory = outputDirectory; } - private void addFileToJar(JarOutputStream outputStream, File file, String targetPath) throws IOException { - byte[] bytes = Files.readAllBytes(file.toPath()); - JarEntry entry = new JarEntry(targetPath); - entry.setTime(0); - entry.setSize(bytes.length); - outputStream.putNextEntry(entry); - outputStream.write(bytes); - outputStream.closeEntry(); + @OutputDirectory + public File getOutputDirectory() { + return outputDirectory; } } diff --git a/build-logic/src/main/java/brainwine/build/JarBundler.java b/build-logic/src/main/java/brainwine/build/JarBundler.java new file mode 100644 index 00000000..f36b7eb8 --- /dev/null +++ b/build-logic/src/main/java/brainwine/build/JarBundler.java @@ -0,0 +1,60 @@ +package brainwine.build; + +import static brainwine.bootstrap.Constants.DIRECTORY_INDEX_FILE; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.Collection; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +import brainwine.bootstrap.DirectoryIndex; +import brainwine.bootstrap.SHA256; + +public class JarBundler implements AutoCloseable { + + private final JarOutputStream outputStream; + + public JarBundler(OutputStream outputStream, Manifest manifest) throws IOException { + this.outputStream = new JarOutputStream(outputStream, manifest); + } + + @Override + public void close() throws IOException { + outputStream.close(); + } + + public void embedDirectory(Collection files, String path) throws IOException { + DirectoryIndex index = new DirectoryIndex(); + + // Add files + for(File file : files) { + String name = file.getName(); + byte[] bytes = Files.readAllBytes(file.toPath()); + String hash = SHA256.hash(bytes); + addFile(bytes, String.format("%s/%s", path, file.getName())); + index.put(name, hash); + } + + // Add index file containing file names and hashes + addEntry(String.format("%s/%s", path, DIRECTORY_INDEX_FILE), index::write); + } + + public void addFile(File file, String name) throws IOException { + addFile(Files.readAllBytes(file.toPath()), name); + } + + public void addFile(byte[] bytes, String name) throws IOException { + addEntry(name, outputStream -> outputStream.write(bytes)); + } + + public void addEntry(String name, OutputStreamWriter writer) throws IOException { + JarEntry entry = new JarEntry(name); + outputStream.putNextEntry(entry); + writer.write(outputStream); + outputStream.closeEntry(); + } +} diff --git a/build-logic/src/main/java/brainwine/build/OutputStreamWriter.java b/build-logic/src/main/java/brainwine/build/OutputStreamWriter.java new file mode 100644 index 00000000..b7f18e56 --- /dev/null +++ b/build-logic/src/main/java/brainwine/build/OutputStreamWriter.java @@ -0,0 +1,10 @@ +package brainwine.build; + +import java.io.IOException; +import java.io.OutputStream; + +@FunctionalInterface +public interface OutputStreamWriter { + + public void write(OutputStream outputStream) throws IOException; +} From 3752888063d41d1626d453d9be707c91fba33b79 Mon Sep 17 00:00:00 2001 From: Kuroppoi Date: Thu, 2 May 2024 17:04:06 +0200 Subject: [PATCH 050/176] Update build.yml --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 774dc16c..855bb0c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,11 +16,11 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v4.1.4 - name: Checkout submodules run: git submodule update --init --recursive - name: Set up JDK 8 - uses: actions/setup-java@v3.6.0 + uses: actions/setup-java@v4.2.1 with: java-version: '8' distribution: 'adopt' @@ -29,7 +29,7 @@ jobs: - name: Build with Gradle run: ./gradlew dist - name: Upload artifact - uses: actions/upload-artifact@v3.1.1 + uses: actions/upload-artifact@v4.3.3 with: name: brainwine path: build/dist/brainwine.jar From 90f29050901e97532130b272a347e82e8a4597bd Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Fri, 3 May 2024 23:50:17 +0200 Subject: [PATCH 051/176] Case-insensitive commands --- .../gameserver/commands/CommandManager.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/CommandManager.java b/gameserver/src/main/java/brainwine/gameserver/commands/CommandManager.java index e82da305..3f80e75a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/CommandManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/CommandManager.java @@ -7,8 +7,11 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -96,8 +99,7 @@ public static void registerCommand(Class type) { return; } - String name = info.name(); - String[] aliases = info.aliases(); + String name = info.name().toLowerCase(); if(commands.containsKey(name)) { logger.warn(SERVER_MARKER, "Attempted to register duplicate command '{}' with name '{}'", type.getSimpleName(), name); @@ -115,7 +117,9 @@ public static void registerCommand(Class type) { commands.put(name, command); - if(aliases != null) { + if(info.aliases() != null) { + List aliases = Stream.of(info.aliases()).map(String::toLowerCase).collect(Collectors.toList()); + for(String alias : aliases) { if(commands.containsKey(alias) || CommandManager.aliases.containsKey(alias)) { logger.warn(SERVER_MARKER, "Duplicate alias {} for command {}", alias, command.getClass()); @@ -139,7 +143,7 @@ public static Command getCommand(String name) { } public static Command getCommand(String name, boolean allowAlias) { - return commands.getOrDefault(name, allowAlias ? aliases.get(name) : null); + return commands.getOrDefault(name.toLowerCase(), allowAlias ? aliases.get(name.toLowerCase()) : null); } public static Collection getCommands() { From 3841f4d54eebde997d110c2040155db98f2fea7f Mon Sep 17 00:00:00 2001 From: Array in a Matrix Date: Fri, 3 May 2024 21:28:42 -0400 Subject: [PATCH 052/176] Add Docker support (#48) --- .dockerignore | 1 + Dockerfile | 27 +++++++++++++++++++++++++++ README.md | 39 ++++++++++++++++++++++++++++++--------- docker-compose.yml | 13 +++++++++++++ 4 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..6d2eaaa4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.github/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..7df02ee2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +#? Builder +FROM --platform=$BUILDPLATFORM debian:bookworm-slim AS builder +RUN apt update -y && apt upgrade -y && apt autoremove -y +RUN apt install -y git gradle +WORKDIR /src +COPY . /src +RUN git submodule init && git submodule update +RUN chmod +x gradlew && ./gradlew dist + +#? Runner +FROM --platform=$BUILDPLATFORM amazoncorretto:22-alpine-jdk AS runner +RUN apk update && apk upgrade +VOLUME ["/data"] +WORKDIR /data +COPY --from=builder /src/build/dist /app +ARG GATEWAY_PORT=5001 +ARG SERVER_PORT=5002 +ARG PORTAL_PORT=5003 +EXPOSE $GATEWAY_PORT $SERVER_PORT $PORTAL_PORT +CMD ["sh", "-c", "java -jar /app/brainwine.jar disablegui"] + +LABEL org.opencontainers.image.title="Brainwine" +LABEL org.opencontainers.image.description=" A portable private server for Deepworld." +LABEL org.opencontainers.image.source="https://github.com/kuroppoi/brainwine" +LABEL org.opencontainers.image.licenses="MIT" +LABEL org.opencontainers.image.documentation="https://github.com/kuroppoi/brainwine" +LABEL org.opencontainers.image.vendor="" diff --git a/README.md b/README.md index 7250b78e..19006c85 100644 --- a/README.md +++ b/README.md @@ -23,27 +23,48 @@ Brainwine currently supports the following versions of Deepworld: ## Building -#### Prerequisites +### Prerequisites - Java 8 Development Kit +Run the following to build the program: + ```sh git clone --recurse-submodules https://github.com/kuroppoi/brainwine.git cd brainwine ./gradlew dist ``` -The output will be located in the `/build/dist` directory. - -## Usage - -Execute `brainwine.jar` to start the program. Navigate to the server tab and press the button to start the server.\ -It is also possible to start the server immediately with no user interface: +The output executable jar `brainwine.jar` will be located in the `/build/dist` directory.\ +To start the server without a user interface, run the following: ```sh # This behavior is the default on platforms that do not support Java's Desktop API. java -jar brainwine.jar disablegui ``` -To connect to a local or remote server, download a [patching kit](https://github.com/kuroppoi/brainwine/releases/tag/patching-kits-1.0) for your desired platform.\ -Alternatively, Windows users may use the program's user interface to configure the host settings and start the game. +## Docker + +Run the following to build the image: + +```sh +git clone https://github.com/kuroppoi/brainwine +cd brainwine +docker buildx build -t brainwine:latest . +``` + +To then run the image in a container, run the following: + +```sh +# Replace ${PWD} with %cd% if you're using a Windows Command Prompt. +docker run -p 5001-5003:5001-5003 --volume ${PWD}/run:/data brainwine:latest +``` + +Or alternatively, if you wish to use docker compose: + +```sh +docker compose up +``` + +The server files will be stored in a docker volume and can be accessed from `/data` in the container.\ +Feel free to play around with the configuration by editing `docker-compose.yml`. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..b07f071e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +services: + brainwine: + # build: . + image: brainwine:latest + ports: + - "5001:5001" # gateway + - "5002:5002" # server + - "5003:5003" # portal + volumes: + - bw-data:/data + +volumes: + bw-data: {} \ No newline at end of file From afa6995f79b0d4dc86237fc2923f0d1a30b12105 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 4 May 2024 16:19:00 +0200 Subject: [PATCH 053/176] Custom OffsetDateTime serializer --- .../java/brainwine/shared/JsonHelper.java | 3 ++ .../shared/OffsetDateTimeSerializer.java | 31 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 shared/src/main/java/brainwine/shared/OffsetDateTimeSerializer.java diff --git a/shared/src/main/java/brainwine/shared/JsonHelper.java b/shared/src/main/java/brainwine/shared/JsonHelper.java index 864c1ce5..83440b75 100644 --- a/shared/src/main/java/brainwine/shared/JsonHelper.java +++ b/shared/src/main/java/brainwine/shared/JsonHelper.java @@ -17,11 +17,14 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; public class JsonHelper { public static final ObjectMapper MAPPER = JsonMapper.builder() .findAndAddModules() + .addModule(new SimpleModule() + .addSerializer(OffsetDateTimeSerializer.INSTANCE)) .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE) .enable(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) diff --git a/shared/src/main/java/brainwine/shared/OffsetDateTimeSerializer.java b/shared/src/main/java/brainwine/shared/OffsetDateTimeSerializer.java new file mode 100644 index 00000000..35080eb5 --- /dev/null +++ b/shared/src/main/java/brainwine/shared/OffsetDateTimeSerializer.java @@ -0,0 +1,31 @@ +package brainwine.shared; + +import java.io.IOException; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +public class OffsetDateTimeSerializer extends StdSerializer { + + public static final OffsetDateTimeSerializer INSTANCE = new OffsetDateTimeSerializer(); + private static final long serialVersionUID = 7309329981624380784L; + private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder() + .appendPattern("yyyy-MM-dd") + .appendLiteral('T') + .appendPattern("HH:mm:ss.SSS") + .appendOffsetId() + .toFormatter(); + + protected OffsetDateTimeSerializer() { + super(OffsetDateTime.class); + } + + @Override + public void serialize(OffsetDateTime dateTime, JsonGenerator generator, SerializerProvider provider) throws IOException { + generator.writeString(formatter.format(dateTime)); + } +} From e2f6a0621ced869fb92a0752a5bb38f2f3ee3b4e Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 4 May 2024 19:12:20 +0200 Subject: [PATCH 054/176] Make player names in EntityManager not case-sensitive --- .../main/java/brainwine/gameserver/zone/EntityManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java index a39a0468..66b565d5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java @@ -298,7 +298,7 @@ public void addEntity(Entity entity) { Player player = (Player)entity; player.onZoneChanged(); players.put(entityId, player); - playersByName.put(player.getName(), player); + playersByName.put(player.getName().toLowerCase(), player); player.sendMessageToPeers(new EntityStatusMessage(player, EntityStatus.ENTERING)); player.sendMessageToPeers(new EntityPositionMessage(player)); } else if(entity instanceof Npc) { @@ -317,7 +317,7 @@ public void removeEntity(Entity entity) { if(entity instanceof Player) { players.remove(entityId); - playersByName.remove(entity.getName()); + playersByName.remove(entity.getName().toLowerCase()); zone.sendMessage(new EntityStatusMessage(entity, EntityStatus.EXITING)); } else { npcs.remove(entityId); @@ -369,7 +369,7 @@ public Player getPlayer(int entityId) { } public Player getPlayer(String name) { - return playersByName.get(name); + return playersByName.get(name.toLowerCase()); } public int getPlayerCount() { From 0822dc411bebdffc534ef316cf4ad14d104c2384 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 4 May 2024 22:51:51 +0200 Subject: [PATCH 055/176] Ecological machine assembly --- .../gameserver/commands/admin/EcoCommand.java | 100 ++++++++++++ .../gameserver/item/ItemUseType.java | 8 + .../interactions/ComposterInteraction.java | 20 +++ .../interactions/ContainerInteraction.java | 7 + .../EcologicalMachineInteraction.java | 39 +++++ .../interactions/ExpiatorInteraction.java | 20 +++ .../item/interactions/GeckInteraction.java | 20 +++ .../interactions/RecyclerInteraction.java | 20 +++ .../gameserver/zone/EcologicalMachine.java | 106 +++++++++++++ .../java/brainwine/gameserver/zone/Zone.java | 148 ++++++++++++++++++ .../gameserver/zone/ZoneConfigFile.java | 18 ++- .../gen/tasks/StructureGeneratorTask.java | 81 +++++++++- 12 files changed, 577 insertions(+), 10 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/commands/admin/EcoCommand.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/ComposterInteraction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/EcologicalMachineInteraction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/ExpiatorInteraction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/GeckInteraction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/RecyclerInteraction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/zone/EcologicalMachine.java diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/EcoCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/EcoCommand.java new file mode 100644 index 00000000..97d269f6 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/EcoCommand.java @@ -0,0 +1,100 @@ +package brainwine.gameserver.commands.admin; + +import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; + +import java.util.Arrays; +import java.util.List; + +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.ItemRegistry; +import brainwine.gameserver.zone.EcologicalMachine; +import brainwine.gameserver.zone.Zone; + +@CommandInfo(name = "eco", description = "Manage ecological machine parts.") +public class EcoCommand extends Command { + + @Override + public void execute(CommandExecutor executor, String[] args) { + if(args.length != 1 && args.length != 3) { + executor.notify(String.format("Usage: %s", getUsage(executor)), SYSTEM); + return; + } + + Player player = (Player)executor; + Zone zone = player.getZone(); + EcologicalMachine machine = EcologicalMachine.fromName(args[0]); + + if(machine == null) { + player.notify(String.format("Machine type must be one of %s", Arrays.toString(EcologicalMachine.values())).toLowerCase(), SYSTEM); + return; + } + + if(args.length == 3) { + String action = args[1]; + Item part = null; + + if(!args[2].equals("all")) { + part = ItemRegistry.getItem(args[2]); + + if(!machine.isMachinePart(part)) { + player.notify(String.format("Machine component must be one of %s", machine.getParts()), SYSTEM); + return; + } + } + + if(action.equals("add")) { + if(part == null) { + machine.getParts().forEach(zone::discoverMachinePart); + zone.discoverMachinePart(machine.getBase()); + player.notify(String.format("Added all %s components.", machine.getId()), SYSTEM); + return; + } + + if(zone.discoverMachinePart(part)) { + player.notify(String.format("Added %s component '%s'", machine.getId(), part.getId()), SYSTEM); + return; + } + + player.notify(String.format("That %s component has already been discovered.", machine.getId()), SYSTEM); + return; + } + + if(action.equals("remove")) { + if(part == null) { + machine.getParts().forEach(x -> zone.removeDiscoveredPart(machine, x)); + zone.removeDiscoveredPart(machine, machine.getBase()); + player.notify(String.format("Removed all %s components.", machine.getId()), SYSTEM); + return; + } + + if(zone.removeDiscoveredPart(machine, part)) { + player.notify(String.format("Removed %s component '%s'", machine.getId(), part.getId()), SYSTEM); + return; + } + + player.notify(String.format("That %s component has not been discovered.", machine.getId()), SYSTEM); + return; + } + + executor.notify(String.format("Usage: %s", getUsage(executor)), SYSTEM); + return; + } + + List parts = zone.getDiscoveredParts(machine); + player.notify(String.format("Discovered %s/%s %s components%s", parts.size(), machine.getPartCount() + 1, machine.getId(), parts.isEmpty() ? "." : ": " + parts), SYSTEM); + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/eco [ ]"; + } + + @Override + public boolean canExecute(CommandExecutor executor) { + return executor.isAdmin() && executor instanceof Player; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java index 15282a7f..0e95ce7d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java @@ -5,10 +5,14 @@ import brainwine.gameserver.item.interactions.BurstInteraction; import brainwine.gameserver.item.interactions.ChangeInteraction; +import brainwine.gameserver.item.interactions.ComposterInteraction; import brainwine.gameserver.item.interactions.ContainerInteraction; import brainwine.gameserver.item.interactions.DialogInteraction; +import brainwine.gameserver.item.interactions.ExpiatorInteraction; +import brainwine.gameserver.item.interactions.GeckInteraction; import brainwine.gameserver.item.interactions.ItemInteraction; import brainwine.gameserver.item.interactions.NoteInteraction; +import brainwine.gameserver.item.interactions.RecyclerInteraction; import brainwine.gameserver.item.interactions.SpawnInteraction; import brainwine.gameserver.item.interactions.SpawnTeleportInteraction; import brainwine.gameserver.item.interactions.SwitchInteraction; @@ -23,10 +27,13 @@ public enum ItemUseType { AFTERBURNER, BURST(new BurstInteraction()), + COMPOSTER(new ComposterInteraction()), CONTAINER(new ContainerInteraction()), CREATE_DIALOG(new DialogInteraction(true)), DESTROY, DIALOG(new DialogInteraction(false)), + EXPIATOR(new ExpiatorInteraction()), + GECK(new GeckInteraction()), GUARD, CHANGE(new ChangeInteraction()), FIELDABLE, @@ -37,6 +44,7 @@ public enum ItemUseType { PLENTY, PROTECTED, PUBLIC, + RECYCLER(new RecyclerInteraction()), SPAWN(new SpawnInteraction()), SPAWN_TELEPORT(new SpawnTeleportInteraction()), SWITCH(new SwitchInteraction()), diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ComposterInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ComposterInteraction.java new file mode 100644 index 00000000..7263ff93 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ComposterInteraction.java @@ -0,0 +1,20 @@ +package brainwine.gameserver.item.interactions; + +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.zone.EcologicalMachine; +import brainwine.gameserver.zone.Zone; + +/** + * Interaction handler for the composter + */ +public class ComposterInteraction extends EcologicalMachineInteraction { + + public ComposterInteraction() { + super(EcologicalMachine.COMPOSTER); + } + + @Override + public void interact(Zone zone, Player player, int x, int y) { + player.notify("Sorry, not implemented yet ;("); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java index 14971f12..d88c35c8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java @@ -4,6 +4,7 @@ import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; import brainwine.gameserver.loot.Loot; @@ -76,6 +77,12 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i } else { player.notify("No eligible loot could be found for this container."); } + } else { + if(zone.discoverMachinePart(player, ItemRegistry.getItem(specialItem))) { + metaBlock.removeProperty("$"); + } else { + // TODO notify player + } } } diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/EcologicalMachineInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/EcologicalMachineInteraction.java new file mode 100644 index 00000000..8185ced1 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/EcologicalMachineInteraction.java @@ -0,0 +1,39 @@ +package brainwine.gameserver.item.interactions; + +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.Layer; +import brainwine.gameserver.zone.EcologicalMachine; +import brainwine.gameserver.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; + +public abstract class EcologicalMachineInteraction implements ItemInteraction { + + protected final EcologicalMachine machine; + + public EcologicalMachineInteraction(EcologicalMachine machine) { + this.machine = machine; + } + + @Override + public final void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, + Object config, Object[] data) { + // Do nothing if entity is not a player + if(!entity.isPlayer()) { + return; + } + + Player player = (Player)entity; + + // Do nothing if the machine cannot be used yet. + if(!zone.canUseMachine(machine, player, x, y)) { + return; + } + + // Handle interaction + interact(zone, player, x, y); + } + + public abstract void interact(Zone zone, Player player, int x, int y); +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ExpiatorInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ExpiatorInteraction.java new file mode 100644 index 00000000..f1ce646d --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ExpiatorInteraction.java @@ -0,0 +1,20 @@ +package brainwine.gameserver.item.interactions; + +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.zone.EcologicalMachine; +import brainwine.gameserver.zone.Zone; + +/** + * Interaction handler for the expiator + */ +public class ExpiatorInteraction extends EcologicalMachineInteraction { + + public ExpiatorInteraction() { + super(EcologicalMachine.EXPIATOR); + } + + @Override + public void interact(Zone zone, Player player, int x, int y) { + player.notify("Sorry, not implemented yet ;("); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/GeckInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/GeckInteraction.java new file mode 100644 index 00000000..8eecb325 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/GeckInteraction.java @@ -0,0 +1,20 @@ +package brainwine.gameserver.item.interactions; + +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.zone.EcologicalMachine; +import brainwine.gameserver.zone.Zone; + +/** + * Interaction handler for the purifier + */ +public class GeckInteraction extends EcologicalMachineInteraction { + + public GeckInteraction() { + super(EcologicalMachine.PURIFIER); + } + + @Override + public void interact(Zone zone, Player player, int x, int y) { + player.notify("The purifier is working."); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/RecyclerInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/RecyclerInteraction.java new file mode 100644 index 00000000..0422ca14 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/RecyclerInteraction.java @@ -0,0 +1,20 @@ +package brainwine.gameserver.item.interactions; + +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.zone.EcologicalMachine; +import brainwine.gameserver.zone.Zone; + +/** + * Interaction handler for the recycler + */ +public class RecyclerInteraction extends EcologicalMachineInteraction { + + public RecyclerInteraction() { + super(EcologicalMachine.RECYCLER); + } + + @Override + public void interact(Zone zone, Player player, int x, int y) { + player.notify("Sorry, not implemented yet ;("); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/EcologicalMachine.java b/gameserver/src/main/java/brainwine/gameserver/zone/EcologicalMachine.java new file mode 100644 index 00000000..7c7419cb --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/zone/EcologicalMachine.java @@ -0,0 +1,106 @@ +package brainwine.gameserver.zone; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.ItemRegistry; + +public enum EcologicalMachine { + + PURIFIER("p", "mechanical/geck-tub", + "mechanical/geck-tank-base", + "mechanical/geck-tank", + "mechanical/geck-hoses", + "mechanical/geck-tree-base", + "mechanical/geck-tree-top", + "mechanical/geck-cog-large", + "mechanical/geck-cog-small"), + + COMPOSTER("c", "mechanical/composter-chamber", + "mechanical/composter-cover", + "mechanical/composter-fuel-tank", + "mechanical/composter-input-tube", + "mechanical/composter-output", + "mechanical/composter-turbine"), + + RECYCLER("r", "mechanical/recycler-chamber", + "mechanical/recycler-tubes", + "mechanical/recycler-pipe-base", + "mechanical/recycler-pipe", + "mechanical/recycler-gear-base", + "mechanical/recycler-gear"), + + // Not exactly 'ecological' but whatever. + EXPIATOR("e", "hell/expiator-face", + "hell/expiator-pipe", + "hell/expiator-frame", + "hell/expiator-tank", + "hell/expiator-tubes", + "hell/expiator-tube", + "hell/expiator-gear", + "hell/expiator-exhaust"); + + private final String clientId; + private final String base; + private final List parts; + + private EcologicalMachine(String clientId, String base, String... parts) { + this.clientId = clientId; + this.base = base; + this.parts = Arrays.asList(parts); + } + + @JsonCreator + public static EcologicalMachine fromName(String id) { + for(EcologicalMachine value : values()) { + if(value.getId().equalsIgnoreCase(id)) { + return value; + } + } + + return null; + } + + public static EcologicalMachine fromBase(Item base) { + return Stream.of(values()).filter(x -> base.hasId(x.base)).findFirst().orElse(null); + } + + public static EcologicalMachine fromPart(Item part) { + return Stream.of(values()).filter(x -> x.isMachinePart(part)).findFirst().orElse(fromBase(part)); + } + + public static boolean isMachine(Item item) { + return fromBase(item) != null; + } + + @JsonValue + public String getId() { + return toString().toLowerCase(); + } + + public String getClientId() { + return clientId; + } + + public Item getBase() { + return ItemRegistry.getItem(base); + } + + public boolean isMachinePart(Item item) { + return item.hasId(base) || parts.contains(item.getId()); + } + + public int getPartCount() { + return parts.size(); + } + + public List getParts() { + return parts.stream().map(ItemRegistry::getItem).collect(Collectors.toList()); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index a23d0400..e6ce0647 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -87,6 +87,8 @@ public class Zone { private final Map metaBlocks = new HashMap<>(); private final Map globalMetaBlocks = new HashMap<>(); private final Map fieldBlocks = new HashMap<>(); + private final Map ecologicalMachineBlocks = new HashMap<>(); + private final Map> discoveredParts = new HashMap<>(); private long lastStatusUpdate = System.currentTimeMillis(); protected Zone(String documentId, ZoneConfigFile config, ZoneDataFile data) { @@ -102,6 +104,7 @@ protected Zone(String documentId, ZoneConfigFile config, ZoneDataFile data) { steamManager.setData(data.getSteamData()); pendingSunlight.addAll(data.getPendingSunlight()); acidity = biome == Biome.ARCTIC || biome == Biome.SPACE ? 0 : config.getAcidity(); + discoveredParts.putAll(config.getDiscoveredParts()); creationDate = config.getCreationDate(); } @@ -849,6 +852,126 @@ public boolean isDungeonIntact(String id) { return dungeons.containsKey(id); } + public void sendMachineStatus(Player player) { + // V3 unfortunately doesn't seem to track machine progress + if(player.isV3()) { + return; + } + + // Get list of ecological machine types that exist in the zone + List machines = ecologicalMachineBlocks.values().stream() + .map(MetaBlock::getItem) + .distinct().map(EcologicalMachine::fromBase) + .collect(Collectors.toList()); + + // Create client data + Map data = machines.stream() + .collect(Collectors.toMap( + x -> String.valueOf(x.getClientId()), // Map machine instance to client ID + x -> discoveredParts.getOrDefault(x, Collections.emptyList()).stream() + .map(Item::getCode) // Map item instance to item code + .collect(Collectors.toList()))); + + // Send data + player.sendMessage(new ZoneStatusMessage(MapHelper.map("machines", data))); + } + + private void updateMachineStatus(EcologicalMachine machine) { + // Update sprite metadata for V3 clients + List metaBlocks = ecologicalMachineBlocks.values().stream() + .filter(x -> x.getItem() == machine.getBase()) + .collect(Collectors.toList()); + + List parts = discoveredParts.getOrDefault(machine, Collections.emptyList()).stream() + .map(Item::getCode) + .collect(Collectors.toList()); + + for(MetaBlock metaBlock : metaBlocks) { + metaBlock.setProperty("spr", parts); + sendBlockMetaUpdate(metaBlock); + } + + // Send machine status update to players + for(Player player : getPlayers()) { + sendMachineStatus(player); + } + } + + public boolean discoverMachinePart(Item part) { + return discoverMachinePart(null, part); + } + + public boolean discoverMachinePart(Player player, Item part) { + EcologicalMachine machine = EcologicalMachine.fromPart(part); + + // Do nothing if machine doesn't exist + if(machine == null) { + return false; + } + + List parts = discoveredParts.computeIfAbsent(machine, x -> new ArrayList<>()); + + // Do nothing if part has already been discovered + if(parts.contains(part)) { + return false; + } + + // Send notifications if part was discovered by a player + if(player != null) { + String machineName = machine.toString().toLowerCase(); + String determiner = Stream.of("a", "e", "i", "o", "u").filter(x -> machineName.startsWith(x)).findFirst().isPresent() ? "an" : "a"; + String text = String.format("You discovered %s %s component!", determiner, machineName); + + if(player.isV3()) { + player.notify(text, NotificationType.ACCOMPLISHMENT); + } else { + Object message = MapHelper.map(String.class, String.class, + "t", text, + "i", part.getId()); + player.notify(message, NotificationType.ACCOMPLISHMENT); + } + + player.notifyPeers(String.format("%s discovered %s %s component.", player.getName(), determiner, machineName), NotificationType.PEER_ACCOMPLISHMENT); + } + + // Update machine status + parts.add(part); + updateMachineStatus(machine); + return true; + } + + public boolean canUseMachine(EcologicalMachine machine, Player player, int x, int y) { + // Check if chunk is loaded + if(!isChunkLoaded(x, y)) { + return false; + } + + int mod = getBlock(x, y).getFrontMod(); + + // Check if machine is already active + if(mod != 0) { + return true; + } + + int totalParts = machine.getPartCount(); + int foundParts = discoveredParts.getOrDefault(machine, Collections.emptyList()).size(); + + // Check if parts have been discovered + if(foundParts < totalParts) { + int remainingParts = totalParts - foundParts; + player.notify(String.format("%s part%s of the %s still need%s to be found.", + remainingParts, remainingParts == 1 ? "" : "s", machine.getId(), remainingParts == 1 ? "s" : "")); + return false; + } + + // Discover the final part and activate the machine! + discoverMachinePart(player, machine.getBase()); + updateBlock(x, y, Layer.FRONT, machine.getBase(), 1, null, getMetaBlock(x, y).getMetadata()); // TODO + player.notify(String.format("You activated the %s!", machine.getId()), NotificationType.ACCOMPLISHMENT); + player.notifyPeers(String.format("%s activated the %s!", player.getName(), machine.getId()), NotificationType.PEER_ACCOMPLISHMENT); + return false; + } + public boolean digBlock(int x, int y) { if(!areCoordinatesInBounds(x, y)) { return false; @@ -1064,12 +1187,20 @@ private void indexMetaBlock(int index, MetaBlock block) { if(item.hasField()) { fieldBlocks.put(index, block); } + + EcologicalMachine machine = EcologicalMachine.fromBase(item); + + if(machine != null) { + ecologicalMachineBlocks.put(index, block); + updateMachineStatus(machine); // TODO this updates ALL machines of this type which isn't really an issue but still kinda scuffed! + } } private void unindexMetaBlock(int index) { metaBlocks.remove(index); globalMetaBlocks.remove(index); fieldBlocks.remove(index); + ecologicalMachineBlocks.remove(index); } protected void setMetaBlocks(List metaBlocks) { @@ -1518,6 +1649,23 @@ public float getAcidity() { return acidity; } + public boolean removeDiscoveredPart(EcologicalMachine machine, Item part) { + if(discoveredParts.containsKey(machine) && discoveredParts.get(machine).remove(part)) { + updateMachineStatus(machine); + return true; + } + + return false; + } + + public List getDiscoveredParts(EcologicalMachine machine) { + return discoveredParts.getOrDefault(machine, Collections.emptyList()); + } + + public Map> getDiscoveredParts() { + return Collections.unmodifiableMap(discoveredParts); + } + public OffsetDateTime getCreationDate() { return creationDate; } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java index 567bc9a4..7a88a00e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java @@ -1,6 +1,9 @@ package brainwine.gameserver.zone; import java.time.OffsetDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -8,6 +11,8 @@ import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.Nulls; +import brainwine.gameserver.item.Item; + @JsonIgnoreProperties(ignoreUnknown = true) public class ZoneConfigFile { @@ -26,19 +31,24 @@ public class ZoneConfigFile { @JsonSetter(nulls = Nulls.SKIP) private float acidity = 1.0F; + @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) + private Map> discoveredParts = new HashMap<>(); + @JsonSetter(nulls = Nulls.SKIP) private OffsetDateTime creationDate = OffsetDateTime.now(); public ZoneConfigFile(Zone zone) { - this(zone.getName(), zone.getBiome(), zone.getWidth(), zone.getHeight(), zone.getAcidity(), zone.getCreationDate()); + this(zone.getName(), zone.getBiome(), zone.getWidth(), zone.getHeight(), zone.getAcidity(), zone.getDiscoveredParts(), zone.getCreationDate()); } - public ZoneConfigFile(String name, Biome biome, int width, int height, float acidity, OffsetDateTime creationDate) { + public ZoneConfigFile(String name, Biome biome, int width, int height, float acidity, + Map> discoveredParts, OffsetDateTime creationDate) { this.name = name; this.biome = biome; this.width = width; this.height = height; this.acidity = acidity; + this.discoveredParts = discoveredParts; this.creationDate = creationDate; } @@ -71,6 +81,10 @@ public float getAcidity() { return acidity; } + public Map> getDiscoveredParts() { + return discoveredParts; + } + public OffsetDateTime getCreationDate() { return creationDate; } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java index c4aa59c5..a1083343 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java @@ -1,12 +1,19 @@ package brainwine.gameserver.zone.gen.tasks; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.stream.Collectors; +import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; import brainwine.gameserver.prefab.Prefab; import brainwine.gameserver.util.Vector2i; import brainwine.gameserver.util.WeightedMap; +import brainwine.gameserver.zone.Biome; +import brainwine.gameserver.zone.EcologicalMachine; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.gen.GeneratorConfig; import brainwine.gameserver.zone.gen.GeneratorContext; @@ -14,8 +21,8 @@ import brainwine.gameserver.zone.gen.caves.CaveDecorator; import brainwine.gameserver.zone.gen.caves.CaveType; import brainwine.gameserver.zone.gen.caves.StructureCaveDecorator; -import brainwine.gameserver.zone.gen.models.TerrainType; import brainwine.gameserver.zone.gen.models.SpecialStructure; +import brainwine.gameserver.zone.gen.models.TerrainType; import brainwine.gameserver.zone.gen.surface.StructureSurfaceDecorator; import brainwine.gameserver.zone.gen.surface.SurfaceDecorator; import brainwine.gameserver.zone.gen.surface.SurfaceRegion; @@ -130,17 +137,75 @@ public void generate(GeneratorContext ctx) { } } - // Populate world with broken teleporters (TODO: world machines and component chests) - // Eligible containers are containers that are below surface and are either a mech chest or a non-dungeon red chest - List replaceableContainers = ctx.getZone().getMetaBlocks(metaBlock + // Fetch list of replaceable containers + List containers = ctx.getZone().getMetaBlocks(metaBlock -> ctx.isUnderground(metaBlock.getX(), metaBlock.getY()) && (metaBlock.getItem().hasId("containers/chest-mechanical-large") || (metaBlock.getItem().hasId("containers/chest") && !metaBlock.getMetadata().containsKey("@")))); - Collections.shuffle(replaceableContainers, ctx.getRandom()); - int brokenTeleporterCount = Math.min(replaceableContainers.size(), Math.max(1, width * height / (ctx.nextInt(80000) + 120000))); + Collections.shuffle(containers, ctx.getRandom()); + + // TODO + placeComponentChests(ctx, containers); + placeBrokenTeleporters(ctx, containers); + + } + + private void placeComponentChests(GeneratorContext ctx, List containers) { + // TODO remove test code + Biome biome = ctx.getZone().getBiome(); + List machines = new ArrayList<>(); + + switch(biome) { + case PLAIN: + machines.add(EcologicalMachine.PURIFIER); + machines.add(ctx.nextDouble() < 0.5 ? EcologicalMachine.RECYCLER : EcologicalMachine.COMPOSTER); + break; + case ARCTIC: + machines.add(ctx.nextDouble() < 0.5 ? EcologicalMachine.RECYCLER : EcologicalMachine.COMPOSTER); + break; + case HELL: + machines.add(EcologicalMachine.EXPIATOR); + break; + case DESERT: + machines.add(EcologicalMachine.PURIFIER); + break; + case DEEP: + machines.add(EcologicalMachine.PURIFIER); + break; + default: + break; + } + + // Get list of machine parts to distribute + List parts = machines.stream().map(EcologicalMachine::getParts).flatMap(Collection::stream).collect(Collectors.toList()); + Iterator iterator = parts.iterator(); + + // Distribute parts + while(iterator.hasNext() && !containers.isEmpty()) { + MetaBlock container = containers.remove(0); + String dungeonId = container.getStringProperty("@"); + int x = container.getX(); + int y = container.getY(); + ctx.updateBlock(x, y, Layer.FRONT, "containers/chest-industrial", 1); + MetaBlock componentChest = ctx.getZone().getMetaBlock(x, y); + componentChest.setProperty("@", dungeonId); + componentChest.setProperty("$", iterator.next().getId()); + iterator.remove(); + } + + // If there are still parts left but no containers, just discover 'em. + for(Item part : parts) { + ctx.getZone().discoverMachinePart(part); + } + } + + private void placeBrokenTeleporters(GeneratorContext ctx, List containers) { + // Calculate the number of broken teleporters to place + int amount = Math.min(containers.size(), Math.max(1, ctx.getWidth() * ctx.getHeight() / (ctx.nextInt(80000) + 120000))); - for(int i = 0; i < brokenTeleporterCount; i++) { - MetaBlock container = replaceableContainers.remove(0); + // Place teleporters + for(int i = 0; i < amount; i++) { + MetaBlock container = containers.remove(0); int x = container.getX(); int y = container.getY(); ctx.updateBlock(x, y, Layer.FRONT, "mechanical/teleporter"); From a4c71588c0fee3bde74da6fb71631e733e3009a6 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 5 May 2024 12:42:39 +0200 Subject: [PATCH 056/176] Track machine part discovery --- gameserver/src/main/java/brainwine/gameserver/zone/Zone.java | 1 + 1 file changed, 1 insertion(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index e6ce0647..0f2445df 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -932,6 +932,7 @@ public boolean discoverMachinePart(Player player, Item part) { } player.notifyPeers(String.format("%s discovered %s %s component.", player.getName(), determiner, machineName), NotificationType.PEER_ACCOMPLISHMENT); + player.getStatistics().trackDiscovery(part); } // Update machine status From dfb80ca890a16c8c27a6c259dafdc3cc3af028df Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 5 May 2024 12:43:15 +0200 Subject: [PATCH 057/176] Notification fix --- .../java/brainwine/gameserver/commands/admin/EcoCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/EcoCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/EcoCommand.java index 97d269f6..ba1119cd 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/EcoCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/EcoCommand.java @@ -29,7 +29,7 @@ public void execute(CommandExecutor executor, String[] args) { EcologicalMachine machine = EcologicalMachine.fromName(args[0]); if(machine == null) { - player.notify(String.format("Machine type must be one of %s", Arrays.toString(EcologicalMachine.values())).toLowerCase(), SYSTEM); + player.notify(String.format("Machine type must be one of %s", Arrays.toString(EcologicalMachine.values()).toLowerCase()), SYSTEM); return; } @@ -47,7 +47,7 @@ public void execute(CommandExecutor executor, String[] args) { } if(action.equals("add")) { - if(part == null) { + if(part == null) { machine.getParts().forEach(zone::discoverMachinePart); zone.discoverMachinePart(machine.getBase()); player.notify(String.format("Added all %s components.", machine.getId()), SYSTEM); From a5f704cc45ec69c7889d4bff30f8c53c7c95076b Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 5 May 2024 17:48:56 +0200 Subject: [PATCH 058/176] Implement Deliverance achievement --- .../gameserver/achievements/Achievement.java | 1 + .../achievements/DeliveranceAchievement.java | 19 ++++++++++++++++++ .../entity/player/PlayerStatistics.java | 20 +++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 gameserver/src/main/java/brainwine/gameserver/achievements/DeliveranceAchievement.java diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/Achievement.java b/gameserver/src/main/java/brainwine/gameserver/achievements/Achievement.java index 00fbfb21..2595daf9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/Achievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievements/Achievement.java @@ -29,6 +29,7 @@ @Type(name = "DiscoveryAchievement", value = DiscoveryAchievement.class), @Type(name = "SpawnerStoppageAchievement", value = SpawnerStoppageAchievement.class), @Type(name = "UndertakerAchievement", value = UndertakerAchievement.class), + @Type(name = "DeliveranceAchievement", value = DeliveranceAchievement.class), @Type(name = "TrappingAchievement", value = TrappingAchievement.class), @Type(name = "Journeyman", value = JourneymanAchievement.class) }) diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/DeliveranceAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievements/DeliveranceAchievement.java new file mode 100644 index 00000000..7e19070d --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/achievements/DeliveranceAchievement.java @@ -0,0 +1,19 @@ +package brainwine.gameserver.achievements; + +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; + +import brainwine.gameserver.entity.player.Player; + +public class DeliveranceAchievement extends Achievement { + + @JsonCreator + public DeliveranceAchievement(@JacksonInject("title") String title) { + super(title); + } + + @Override + public int getProgress(Player player) { + return player.getStatistics().getDeliverances(); + } +} \ No newline at end of file diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java index d628a5e4..350609f0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import brainwine.gameserver.achievements.CraftingAchievement; +import brainwine.gameserver.achievements.DeliveranceAchievement; import brainwine.gameserver.achievements.DiscoveryAchievement; import brainwine.gameserver.achievements.ExploringAchievement; import brainwine.gameserver.achievements.HuntingAchievement; @@ -43,6 +44,7 @@ public class PlayerStatistics { private int dungeonsRaided; private int mawsPlugged; private int undertakings; + private int deliverances; private int deaths; @JsonIgnore @@ -351,6 +353,24 @@ public int getUndertakings() { return undertakings; } + public void trackDeliverance() { + trackDeliverances(1); + } + + public void trackDeliverances(int amount) { + deliverances += amount; + player.addExperience(25 * amount); + player.updateAchievementProgress(DeliveranceAchievement.class); + } + + public void setDeliverances(int deliverances) { + this.deliverances = deliverances; + } + + public int getDeliverances() { + return deliverances; + } + public void trackDeath() { deaths++; } From 9e439a5a4e1bbc7eade0d26fa28744bc83d7ad74 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 5 May 2024 17:49:25 +0200 Subject: [PATCH 059/176] Send machine status on player join --- .../src/main/java/brainwine/gameserver/entity/player/Player.java | 1 + 1 file changed, 1 insertion(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java index 97536a37..940e22a0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java @@ -332,6 +332,7 @@ public void onZoneChanged() { sendMessage(new ConfigurationMessage(id, getClientConfig(), GameConfiguration.getClientConfig(this), zone.getClientConfig(this))); sendMessage(new ZoneStatusMessage(zone.getStatusConfig(this))); + zone.sendMachineStatus(this); sendMessage(new PlayerPositionMessage((int)x, (int)y)); sendMessage(new HealthMessage(health)); From 44fbb5c8a780f4f50465999f5acd87eea6a5a606 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 5 May 2024 17:52:43 +0200 Subject: [PATCH 060/176] Add interaction functionality for ecological machines --- .../interactions/ComposterInteraction.java | 24 ++++++- .../interactions/ExpiatorInteraction.java | 38 ++++++++++- .../interactions/RecyclerInteraction.java | 66 ++++++++++++++++++- 3 files changed, 123 insertions(+), 5 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ComposterInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ComposterInteraction.java index 7263ff93..781bc543 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ComposterInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ComposterInteraction.java @@ -1,6 +1,9 @@ package brainwine.gameserver.item.interactions; +import brainwine.gameserver.entity.player.Inventory; import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.zone.EcologicalMachine; import brainwine.gameserver.zone.Zone; @@ -8,13 +11,30 @@ * Interaction handler for the composter */ public class ComposterInteraction extends EcologicalMachineInteraction { - + + public static final int COMPOST_EARTH_COST = 10; + public static final int COMPOST_GIBLETS_COST = 3; + public ComposterInteraction() { super(EcologicalMachine.COMPOSTER); } @Override public void interact(Zone zone, Player player, int x, int y) { - player.notify("Sorry, not implemented yet ;("); + Inventory inventory = player.getInventory(); + Item earth = ItemRegistry.getItem("ground/earth"); + Item giblets = ItemRegistry.getItem("ground/giblets"); + Item compost = ItemRegistry.getItem("ground/earth-compost"); + + // Check if player has the required items + if(!inventory.hasItem(earth, COMPOST_EARTH_COST) || !inventory.hasItem(giblets, COMPOST_GIBLETS_COST)) { + player.notify(String.format("You need %s earth and %s giblets to generate compost.", COMPOST_EARTH_COST, COMPOST_GIBLETS_COST)); + return; + } + + inventory.removeItem(earth, COMPOST_EARTH_COST, true); + inventory.removeItem(giblets, COMPOST_GIBLETS_COST, true); + inventory.addItem(compost, true); + zone.spawnEffect(x + 2.0F, y, "area steam", 10); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ExpiatorInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ExpiatorInteraction.java index f1ce646d..1e8fe102 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ExpiatorInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ExpiatorInteraction.java @@ -1,7 +1,15 @@ package brainwine.gameserver.item.interactions; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.player.NotificationType; import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Layer; import brainwine.gameserver.zone.EcologicalMachine; +import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; /** @@ -15,6 +23,34 @@ public ExpiatorInteraction() { @Override public void interact(Zone zone, Player player, int x, int y) { - player.notify("Sorry, not implemented yet ;("); + // TODO create a more generic function for this + List ghosts = zone.getNpcs().stream() + .filter(npc -> npc.getConfig().getName().equals("ghost") && npc.inRange(x, y, 5.0)) + .collect(Collectors.toList()); + + // Check if there are ghosts nearby + if(ghosts.isEmpty()) { + player.notify("No ghosts in range."); + return; + } + + List protectors = zone.getMetaBlocksWithItem("hell/dish"); + Collections.shuffle(protectors); + + // Expiate nearby ghosts + for(Entity ghost : ghosts) { + ghost.setHealth(0.0F); + + // Destroy a random infernal protector + if(!protectors.isEmpty()) { + MetaBlock protector = protectors.remove(0); + zone.updateBlock(protector.getX(), protector.getY(), Layer.FRONT, 0); + } + } + + zone.spawnEffect(x + 2.0F, y, "expiate", 10); + player.notify("You released a lost soul!", NotificationType.ACCOMPLISHMENT); + player.notifyPeers(String.format("%s released a lost soul.", player.getName()), NotificationType.PEER_ACCOMPLISHMENT); + player.getStatistics().trackDeliverances(ghosts.size()); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/RecyclerInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/RecyclerInteraction.java index 0422ca14..0a611d48 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/RecyclerInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/RecyclerInteraction.java @@ -1,6 +1,20 @@ package brainwine.gameserver.item.interactions; +import java.util.Map; +import java.util.Map.Entry; + +import brainwine.gameserver.dialog.Dialog; +import brainwine.gameserver.dialog.DialogListItem; +import brainwine.gameserver.dialog.DialogSection; +import brainwine.gameserver.dialog.DialogType; +import brainwine.gameserver.entity.player.Inventory; +import brainwine.gameserver.entity.player.NotificationType; import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.entity.player.Skill; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.ItemRegistry; +import brainwine.gameserver.util.MapHelper; +import brainwine.gameserver.util.MathUtils; import brainwine.gameserver.zone.EcologicalMachine; import brainwine.gameserver.zone.Zone; @@ -8,13 +22,61 @@ * Interaction handler for the recycler */ public class RecyclerInteraction extends EcologicalMachineInteraction { - + + private static final Map recyclables = MapHelper.map(Item.class, Item.class, + ItemRegistry.getItem("rubble/iron"), ItemRegistry.getItem("building/iron"), + ItemRegistry.getItem("rubble/copper"), ItemRegistry.getItem("building/copper"), + ItemRegistry.getItem("rubble/brass"), ItemRegistry.getItem("building/brass")); + public RecyclerInteraction() { super(EcologicalMachine.RECYCLER); } @Override public void interact(Zone zone, Player player, int x, int y) { - player.notify("Sorry, not implemented yet ;("); + int totalScrapRecycled = 0; + int scrapRequired = (int)MathUtils.lerp(10.0, 5.0, player.getTotalSkillLevel(Skill.BUILDING) / 9.0); + Inventory inventory = player.getInventory(); + DialogSection section = new DialogSection(); + + // Recycle scrap + for(Entry entry : recyclables.entrySet()) { + Item scrapItem = entry.getKey(); + int recycleCount = inventory.getQuantity(scrapItem) / scrapRequired; + + // Skip if there isn't enough scrap of this type to recycle + if(recycleCount == 0) { + continue; + } + + // Update inventory + Item resultItem = entry.getValue(); + int scrapCount = recycleCount * scrapRequired; + totalScrapRecycled += scrapCount; + inventory.removeItem(scrapItem, scrapCount, true); + inventory.addItem(resultItem, recycleCount, true); + + // Create dialog item + section.addItem(new DialogListItem() + .setItem(resultItem.getCode()) + .setText(String.format("%s x %s", resultItem.getTitle(), recycleCount))); + } + + // Check if anything was recycled + if(totalScrapRecycled == 0) { + player.notify("You do not have enough scrap to recycle."); + return; + } + + zone.spawnEffect(x + 2.0F, y, "area steam", 10); + Dialog dialog = new Dialog() + .addSection(section.setTitle(String.format("You recycled %s scrap into:", totalScrapRecycled))); + + // Show result dialog + if(player.isV3()) { + player.showDialog(dialog.setType(DialogType.LOOT)); + } else { + player.notify(dialog, NotificationType.REWARD); + } } } From 44e1dee46a94c64f3db0e3f1a578a1a28e953aed Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 5 May 2024 17:52:55 +0200 Subject: [PATCH 061/176] Add `/find` command --- .../commands/admin/FindCommand.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 gameserver/src/main/java/brainwine/gameserver/commands/admin/FindCommand.java diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/FindCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/FindCommand.java new file mode 100644 index 00000000..e23e7817 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/FindCommand.java @@ -0,0 +1,41 @@ +package brainwine.gameserver.commands.admin; + +import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; + +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.zone.MetaBlock; + +@CommandInfo(name = "find", description = "Displays the location of a random meta block of the specified type.") +public class FindCommand extends Command { + + @Override + public void execute(CommandExecutor executor, String[] args) { + if(args.length == 0) { + executor.notify(String.format("Usage: %s", getUsage(executor)), SYSTEM); + return; + } + + Player player = (Player)executor; + MetaBlock metaBlock = player.getZone().getMetaBlocks().stream().filter(x -> x.getItem().hasId(args[0])).findAny().orElse(null); + + if(metaBlock == null) { + player.notify(String.format("No meta block of type '%s' exists in this zone.", args[0]), SYSTEM); + return; + } + + player.notify(String.format("Target found at X: %s, Y: %s", metaBlock.getX(), metaBlock.getY()), SYSTEM); + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/find "; + } + + @Override + public boolean canExecute(CommandExecutor executor) { + return executor.isAdmin() && executor instanceof Player; + } +} From 0b5ab23b8c254a75ef0db6e269816f40e825ac46 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 5 May 2024 18:46:19 +0200 Subject: [PATCH 062/176] Fix recycler --- .../item/interactions/RecyclerInteraction.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/RecyclerInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/RecyclerInteraction.java index 0a611d48..33cd17f2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/RecyclerInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/RecyclerInteraction.java @@ -23,17 +23,16 @@ */ public class RecyclerInteraction extends EcologicalMachineInteraction { - private static final Map recyclables = MapHelper.map(Item.class, Item.class, - ItemRegistry.getItem("rubble/iron"), ItemRegistry.getItem("building/iron"), - ItemRegistry.getItem("rubble/copper"), ItemRegistry.getItem("building/copper"), - ItemRegistry.getItem("rubble/brass"), ItemRegistry.getItem("building/brass")); - public RecyclerInteraction() { super(EcologicalMachine.RECYCLER); } @Override public void interact(Zone zone, Player player, int x, int y) { + Map recyclables = MapHelper.map(Item.class, Item.class, + ItemRegistry.getItem("rubble/iron"), ItemRegistry.getItem("building/iron"), + ItemRegistry.getItem("rubble/copper"), ItemRegistry.getItem("building/copper"), + ItemRegistry.getItem("rubble/brass"), ItemRegistry.getItem("building/brass")); int totalScrapRecycled = 0; int scrapRequired = (int)MathUtils.lerp(10.0, 5.0, player.getTotalSkillLevel(Skill.BUILDING) / 9.0); Inventory inventory = player.getInventory(); From c954a2a6a822733267571659d1d4cccd32ba1bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Mon, 6 May 2024 22:59:00 +0300 Subject: [PATCH 063/176] Add automatic zone generation based on exploration condition (#44) --- .../server/requests/ZoneSearchRequest.java | 4 +- .../java/brainwine/gameserver/zone/Zone.java | 8 ++++ .../gameserver/zone/ZoneManager.java | 44 +++++++++++++++++++ 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneSearchRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneSearchRequest.java index 7ccfbeea..d6212248 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneSearchRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneSearchRequest.java @@ -71,10 +71,10 @@ private List searchZones(ZoneManager manager) { zones.addAll(manager.searchZones(zone -> zone.getBiome() == Biome.SPACE)); break; case "Unexplored": - zones.addAll(manager.searchZones(zone -> zone.getExplorationProgress() < 0.7, (a, b) -> Float.compare(a.getExplorationProgress(), b.getExplorationProgress()))); + zones.addAll(manager.searchZones(Zone::isUnexplored, (a, b) -> Float.compare(a.getExplorationProgress(), b.getExplorationProgress()))); break; case "Popular": - zones.addAll(manager.searchZones(zone -> zone.getPlayers().size() > 0, (a, b) -> Integer.compare(b.getPlayers().size(), a.getPlayers().size()))); + zones.addAll(manager.searchZones(Zone::isPopular, (a, b) -> Integer.compare(b.getPlayers().size(), a.getPlayers().size()))); break; default: zones.addAll(manager.searchZones(zone -> zone.getName().toLowerCase().contains(type.toLowerCase()))); diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 0f2445df..9e94e6b3 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -1670,6 +1670,14 @@ public Map> getDiscoveredParts() { public OffsetDateTime getCreationDate() { return creationDate; } + + public boolean isUnexplored() { + return this.getExplorationProgress() < 0.7; + } + + public boolean isPopular() { + return this.getPlayers().size() > 0; + } /** * @return A {@link Map} containing all the data necessary for use in {@link ConfigurationMessage}. diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java index f8510c5e..4d0ed756 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java @@ -38,6 +38,8 @@ public class ZoneManager { private final File dataDir = new File("zones"); private Map zones = new HashMap<>(); private Map zonesByName = new HashMap<>(); + private long lastZoneGenerationTime = System.currentTimeMillis(); + private boolean generatingZone = false; public ZoneManager() { logger.info(SERVER_MARKER, "Loading zone data ..."); @@ -73,6 +75,40 @@ public void tick(float deltaTime) { for(Zone zone : getZones()) { zone.tick(deltaTime); } + + long timeSinceLastGeneration = (System.currentTimeMillis() - lastZoneGenerationTime) / 1000; + + // zero players interval has to be greater than the min generation interval + final long MIN_GENERATION_INTERVAL_SECONDS = 10 * 60; + final long GENERATION_INTERVAL_ZERO_PLAYERS_SECONDS = 30 * 60; + // player count influence has to be positive and a greater value means + // more players are needed for a given increase in generation rate + final long PLAYER_COUNT_INFLUENCE = 16; + + if (!generatingZone && timeSinceLastGeneration > MIN_GENERATION_INTERVAL_SECONDS) { + int playerCount = zones.values().stream().map(Zone::getPlayerCount).reduce(Integer::sum).orElse(0); + long requiredInterval = Math.max( + MIN_GENERATION_INTERVAL_SECONDS, + GENERATION_INTERVAL_ZERO_PLAYERS_SECONDS - (playerCount - 1) * (GENERATION_INTERVAL_ZERO_PLAYERS_SECONDS - MIN_GENERATION_INTERVAL_SECONDS) / PLAYER_COUNT_INFLUENCE + ); + + if (timeSinceLastGeneration > requiredInterval) { + if (shouldGenerateUnexploredZone() && !generatingZone) { + generatingZone = true; + Biome biome = Biome.getRandomBiome(); + ZoneGenerator generator = ZoneGenerator.getZoneGenerator(biome); + generator.generateZoneAsync(biome, zone -> { + if (zone != null) { + this.addZone(zone); + lastZoneGenerationTime = System.currentTimeMillis(); + } else { + logger.warn(SERVER_MARKER, "Automatic zone generation failed. See the previous logs for more information."); + } + generatingZone = false; + }); + } + } + } } public void onShutdown() { @@ -191,6 +227,14 @@ public void addZone(Zone zone) { zones.put(id, zone); zonesByName.put(name.toLowerCase(), zone); } + + /**Should the game create a new world because all the worlds are established? + * + * @return true iff the game should create a new world at next opportunity + */ + public boolean shouldGenerateUnexploredZone() { + return getZones().stream().allMatch(zone -> zone.getExplorationProgress() >= 0.4); + } public Zone getZone(String id) { return zones.get(id); From 78faef80f3dc9bb30574642c8d3916080052f975 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 7 May 2024 01:23:46 +0200 Subject: [PATCH 064/176] Implement field damage --- .../gameserver/item/FieldDamage.java | 23 +++++++++++++ .../java/brainwine/gameserver/item/Item.java | 11 +++++++ .../java/brainwine/gameserver/zone/Zone.java | 32 +++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/FieldDamage.java diff --git a/gameserver/src/main/java/brainwine/gameserver/item/FieldDamage.java b/gameserver/src/main/java/brainwine/gameserver/item/FieldDamage.java new file mode 100644 index 00000000..be3e9bc2 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/FieldDamage.java @@ -0,0 +1,23 @@ +package brainwine.gameserver.item; + +import com.fasterxml.jackson.annotation.JsonFormat; + +@JsonFormat(shape = JsonFormat.Shape.ARRAY) +public class FieldDamage { + + private DamageType type; + private float maxDamage; + private float radius; + + public DamageType getType() { + return type; + } + + public float getMaxDamage() { + return maxDamage; + } + + public float getRadius() { + return radius; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/Item.java b/gameserver/src/main/java/brainwine/gameserver/item/Item.java index 605dcc8e..e1a1c777 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/Item.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/Item.java @@ -171,6 +171,9 @@ public class Item { @JsonProperty("timer_mine") private boolean processTimerOnBreak; + @JsonProperty("field_damage") + private FieldDamage fieldDamage; + @JsonProperty("ingredients") private List craftingIngredients = new ArrayList<>(); @@ -505,6 +508,14 @@ public boolean shouldProcessTimerOnBreak() { return processTimerOnBreak; } + public boolean hasFieldDamage() { + return fieldDamage != null; + } + + public FieldDamage getFieldDamage() { + return fieldDamage; + } + public boolean isCraftable() { return !craftingIngredients.isEmpty(); } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 9e94e6b3..73a42841 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -87,9 +87,11 @@ public class Zone { private final Map metaBlocks = new HashMap<>(); private final Map globalMetaBlocks = new HashMap<>(); private final Map fieldBlocks = new HashMap<>(); + private final Map damageFieldBlocks = new HashMap<>(); private final Map ecologicalMachineBlocks = new HashMap<>(); private final Map> discoveredParts = new HashMap<>(); private long lastStatusUpdate = System.currentTimeMillis(); + private int ticksElapsed; protected Zone(String documentId, ZoneConfigFile config, ZoneDataFile data) { this(documentId, config.getName(), config.getBiome(), config.getWidth(), config.getHeight()); @@ -178,6 +180,31 @@ public void tick(float deltaTime) { blockChanges.clear(); } + + + // Process field damage (every 1 second) + if(ticksElapsed % 8 == 0) { + for(Player player : getPlayers()) { + for(MetaBlock block : damageFieldBlocks.values()) { + Item item = block.getItem(); + float distance = (float)MathUtils.distance(player.getX(), player.getY(), block.getX(), block.getY()); + float radius = item.getFieldDamage().getRadius(); + float maxDamage = item.getFieldDamage().getMaxDamage(); + + if(maxDamage == 0.0F) { + maxDamage = 2.0F; + } + + // Deal true damage to the target, scaling with distance from field block + if(distance < radius) { + float damage = maxDamage * (1.0F - distance / radius); + player.attack(null, item, damage, item.getFieldDamage().getType(), true); + } + } + } + } + + ticksElapsed++; } /** @@ -1189,6 +1216,10 @@ private void indexMetaBlock(int index, MetaBlock block) { fieldBlocks.put(index, block); } + if(item.hasFieldDamage()) { + damageFieldBlocks.put(index, block); + } + EcologicalMachine machine = EcologicalMachine.fromBase(item); if(machine != null) { @@ -1201,6 +1232,7 @@ private void unindexMetaBlock(int index) { metaBlocks.remove(index); globalMetaBlocks.remove(index); fieldBlocks.remove(index); + damageFieldBlocks.remove(index); ecologicalMachineBlocks.remove(index); } From a2dc3fff38bbcc232fb6f1b5d829bb77de92a702 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 7 May 2024 01:24:14 +0200 Subject: [PATCH 065/176] Delete unused class --- .../brainwine/gameserver/zone/DugBlock.java | 40 ------------------- 1 file changed, 40 deletions(-) delete mode 100644 gameserver/src/main/java/brainwine/gameserver/zone/DugBlock.java diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/DugBlock.java b/gameserver/src/main/java/brainwine/gameserver/zone/DugBlock.java deleted file mode 100644 index fb385764..00000000 --- a/gameserver/src/main/java/brainwine/gameserver/zone/DugBlock.java +++ /dev/null @@ -1,40 +0,0 @@ -package brainwine.gameserver.zone; - -import brainwine.gameserver.item.Item; - -public class DugBlock { - - private final int x; - private final int y; - private final Item item; - private final int mod; - private final long time; - - public DugBlock(int x, int y, Item item, int mod, long time) { - this.x = x; - this.y = y; - this.item = item; - this.mod = mod; - this.time = time; - } - - public int getX() { - return x; - } - - public int getY() { - return y; - } - - public Item getItem() { - return item; - } - - public int getMod() { - return mod; - } - - public long getTime() { - return time; - } -} From 0ca51a11e64550806aca65b29eb38a4563d27c11 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 7 May 2024 02:09:25 +0200 Subject: [PATCH 066/176] Generate infernal protectors in hell biomes --- .../gen/tasks/StructureGeneratorTask.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java index a1083343..802a513e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java @@ -148,6 +148,10 @@ public void generate(GeneratorContext ctx) { placeComponentChests(ctx, containers); placeBrokenTeleporters(ctx, containers); + if(ctx.getZone().getBiome() == Biome.HELL) { + placeInfernalProtectors(ctx, containers); + } + } private void placeComponentChests(GeneratorContext ctx, List containers) { @@ -213,6 +217,25 @@ private void placeBrokenTeleporters(GeneratorContext ctx, List contai } } + private void placeInfernalProtectors(GeneratorContext ctx, List containers) { + // One dish per 100,000 blocks (12 dishes in a normal sized world) + int amount = Math.min(containers.size(), Math.max(1, ctx.getWidth() * ctx.getHeight() / 100000)); + + for(int i = 0; i < amount; i++) { + MetaBlock container = containers.remove(i); + int x = container.getX(); + int y = container.getY(); + + // Replace with large chest if container is a small chest + if(container.getItem().hasId("containers/chest")) { + ctx.updateBlock(x, y, Layer.FRONT, "containers/chest-large", 1, container.getMetadata()); + } + + // Place dish on top of the container + ctx.updateBlock(x, y - 1, Layer.FRONT, "hell/dish"); + } + } + private void placeRandomSpawnBuilding(GeneratorContext ctx, int x) { Prefab spawnBuilding = spawnBuildings.next(ctx.getRandom()); From 9c9dfa46d64d6c5356c633aa2b25d64410c25c0f Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 7 May 2024 02:11:08 +0200 Subject: [PATCH 067/176] Make teleporter spawn count not random --- .../gameserver/zone/gen/tasks/StructureGeneratorTask.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java index 802a513e..3873a86f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java @@ -204,8 +204,8 @@ private void placeComponentChests(GeneratorContext ctx, List containe } private void placeBrokenTeleporters(GeneratorContext ctx, List containers) { - // Calculate the number of broken teleporters to place - int amount = Math.min(containers.size(), Math.max(1, ctx.getWidth() * ctx.getHeight() / (ctx.nextInt(80000) + 120000))); + // One teleporter per 150,000 blocks (8 teleporters in a normal sized world) + int amount = Math.min(containers.size(), Math.max(1, ctx.getWidth() * ctx.getHeight() / 150000)); // Place teleporters for(int i = 0; i < amount; i++) { From d9b009f3df6273af6b8aae3d9ff8022fb12c81b0 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 7 May 2024 02:13:28 +0200 Subject: [PATCH 068/176] Document functions & minor changes --- .../main/java/brainwine/gameserver/zone/Zone.java | 15 ++++++++++++++- .../zone/gen/tasks/StructureGeneratorTask.java | 3 ++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 73a42841..965b0d6c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -879,6 +879,10 @@ public boolean isDungeonIntact(String id) { return dungeons.containsKey(id); } + /** + * This will show the number of discovered components on the minimap + * as well as update the machine sprite on V2 clients. + */ public void sendMachineStatus(Player player) { // V3 unfortunately doesn't seem to track machine progress if(player.isV3()) { @@ -886,6 +890,7 @@ public void sendMachineStatus(Player player) { } // Get list of ecological machine types that exist in the zone + // TODO this isn't necessary: it originally only showed minimap status of machines that have had components discovered. List machines = ecologicalMachineBlocks.values().stream() .map(MetaBlock::getItem) .distinct().map(EcologicalMachine::fromBase) @@ -903,16 +908,23 @@ public void sendMachineStatus(Player player) { player.sendMessage(new ZoneStatusMessage(MapHelper.map("machines", data))); } + /** + * Sync machine status with all players in the zone. + */ private void updateMachineStatus(EcologicalMachine machine) { - // Update sprite metadata for V3 clients + // Find all machines of this type in the zone List metaBlocks = ecologicalMachineBlocks.values().stream() .filter(x -> x.getItem() == machine.getBase()) .collect(Collectors.toList()); + // Get list of discovered parts and transform to client data List parts = discoveredParts.getOrDefault(machine, Collections.emptyList()).stream() + .filter(machine::isMachinePart) .map(Item::getCode) .collect(Collectors.toList()); + parts.add(machine.getBase().getCode()); + // Update the machine sprite for V3 clients for(MetaBlock metaBlock : metaBlocks) { metaBlock.setProperty("spr", parts); sendBlockMetaUpdate(metaBlock); @@ -944,6 +956,7 @@ public boolean discoverMachinePart(Player player, Item part) { } // Send notifications if part was discovered by a player + // TODO machine base shouldn't do this if(player != null) { String machineName = machine.toString().toLowerCase(); String determiner = Stream.of("a", "e", "i", "o", "u").filter(x -> machineName.startsWith(x)).findFirst().isPresent() ? "an" : "a"; diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java index 3873a86f..dc64ae0a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java @@ -165,13 +165,14 @@ private void placeComponentChests(GeneratorContext ctx, List containe machines.add(ctx.nextDouble() < 0.5 ? EcologicalMachine.RECYCLER : EcologicalMachine.COMPOSTER); break; case ARCTIC: - machines.add(ctx.nextDouble() < 0.5 ? EcologicalMachine.RECYCLER : EcologicalMachine.COMPOSTER); + machines.add(EcologicalMachine.RECYCLER); break; case HELL: machines.add(EcologicalMachine.EXPIATOR); break; case DESERT: machines.add(EcologicalMachine.PURIFIER); + machines.add(EcologicalMachine.RECYCLER); break; case DEEP: machines.add(EcologicalMachine.PURIFIER); From 64bfe487ad27f3921a9b396e3ce3d427d9c9653d Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 7 May 2024 11:44:52 +0200 Subject: [PATCH 069/176] Remove exit calls --- .../java/brainwine/gameserver/GameConfiguration.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java b/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java index f356533f..6dbc0a81 100644 --- a/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java +++ b/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java @@ -22,8 +22,6 @@ import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; -import com.fasterxml.jackson.core.JsonProcessingException; - import brainwine.gameserver.commands.CommandManager; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.entity.player.Skill; @@ -162,9 +160,9 @@ private static void configure() { try { Item item = JsonHelper.readValue(config, Item.class); ItemRegistry.registerItem(item); - } catch (JsonProcessingException e) { - logger.fatal(SERVER_MARKER, "Failed to register item {}", id, e); - System.exit(0); + } catch (Exception e) { + logger.fatal(SERVER_MARKER, "Failed to register item '{}'", id, e); + throw new RuntimeException(e); // Server SHOULD NOT attempt to start if there is a problem with the item configuration } }); @@ -202,7 +200,7 @@ private static void loadConfigFiles() { } } catch(Exception e) { logger.fatal(SERVER_MARKER, "Could not load configuration files", e); - System.exit(-1); + throw new RuntimeException(e); // Server SHOULD NOT attempt to start if the game configuration can't be loaded } } From da598adec1e038aa0ebc609aeea587431c43fc31 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 7 May 2024 13:42:02 +0200 Subject: [PATCH 070/176] Update block mod directly in steam update (#46) --- .../item/interactions/SwitchInteraction.java | 10 +++++++++- .../java/brainwine/gameserver/zone/SteamManager.java | 11 ++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java index 8096fd13..662ce66f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java @@ -145,10 +145,18 @@ private void switchExploder(Zone zone, Entity entity, MetaBlock metaBlock) { return; } + int x = metaBlock.getX(); + int y = metaBlock.getY(); + + // Do nothing if exploder isn't activated + if(zone.getBlock(x, y).getFrontMod() == 0) { + return; + } + // Create explosion DamageType damageType = type.equalsIgnoreCase("electric") ? DamageType.ENERGY : DamageType.fromName(type); String effect = String.format("bomb-%s", type.toLowerCase()); - zone.explode(metaBlock.getX(), metaBlock.getY(), 6, entity, false, 6, damageType, effect); + zone.explode(x, y, 6, entity, false, 6, damageType, effect); } private void switchSign(Zone zone, Entity entity, MetaBlock metaBlock, MetaBlock switchMeta) { diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/SteamManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/SteamManager.java index cc86858d..b7beafdd 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/SteamManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/SteamManager.java @@ -8,6 +8,7 @@ import java.util.Set; import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.Layer; /** * Distributes steam through collectors to nearby machines via pipes. @@ -65,7 +66,8 @@ private void updateSteam() { continue; } - Item item = zone.getBlock(x, y).getFrontItem(); + Block block = zone.getBlock(x, y); + Item item = block.getFrontItem(); // Skip if front item doesn't use steam if(!item.usesSteam()) { @@ -73,8 +75,8 @@ private void updateSteam() { continue; } - // Update block - zone.updateBlock(x, y, item.getLayer(), item, 0); + // Directly set the block mod + block.setMod(Layer.FRONT, 0); } // Unindex expired steamables @@ -131,8 +133,7 @@ private void updateSteam() { // ...but activate it first if it uses steam! if(steamableIndices.contains(index)) { - Item item = zone.getBlock(x, y).getFrontItem(); - zone.updateBlock(x, y, item.getLayer(), item, 1); + zone.getBlock(x, y).setMod(Layer.FRONT, 1); // Directly set the block mod } continue; From 2d5913cc2b78f673c326dc1980e726e1cc667e58 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 7 May 2024 14:14:25 +0200 Subject: [PATCH 071/176] Quick fix --- .../brainwine/gameserver/zone/SteamManager.java | 8 +++----- .../main/java/brainwine/gameserver/zone/Zone.java | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/SteamManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/SteamManager.java index b7beafdd..03c2ac25 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/SteamManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/SteamManager.java @@ -76,7 +76,7 @@ private void updateSteam() { } // Directly set the block mod - block.setMod(Layer.FRONT, 0); + zone.updateBlockMod(x, y, Layer.FRONT, 0); } // Unindex expired steamables @@ -128,12 +128,10 @@ private void updateSteam() { processedIndices.add(index); - // Skip if block is not a pipe + // Skip if block is not a pipe but activate it first if it uses steam if(getState(x, y) != STATE_PIPE) { - - // ...but activate it first if it uses steam! if(steamableIndices.contains(index)) { - zone.getBlock(x, y).setMod(Layer.FRONT, 1); // Directly set the block mod + zone.updateBlockMod(x, y, Layer.FRONT, 1); // Directly set the block mod } continue; diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 965b0d6c..a2ecdf39 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -1145,6 +1145,20 @@ public void updateBlock(int x, int y, Layer layer, Item item, int mod, Player ow } } + // TODO better block update methods + protected void updateBlockMod(int x, int y, Layer layer, int mod) { + if(!areCoordinatesInBounds(x, y)) { + return; + } + + Block block = getBlock(x, y); + block.setMod(layer, mod); + + if(!getPlayers().isEmpty()) { + blockChanges.add(new BlockChangeData(x, y, layer, block.getItem(layer), mod)); + } + } + /** * @param x The x position of the block. * @param y The y position of the block. From e38d4ced01e267657f9a5294fc89878137a5363f Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 7 May 2024 15:58:22 +0200 Subject: [PATCH 072/176] Always unindex MetaBlock before re-indexing it --- gameserver/src/main/java/brainwine/gameserver/zone/Zone.java | 1 + 1 file changed, 1 insertion(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index a2ecdf39..643f7a44 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -1232,6 +1232,7 @@ public void setMetaBlock(int x, int y, Item item, Player owner, Map Date: Wed, 8 May 2024 23:43:20 +0200 Subject: [PATCH 073/176] Move some things around & add purifier functionality --- .../gameserver/commands/admin/EcoCommand.java | 16 +- .../interactions/ContainerInteraction.java | 30 ++- .../EcologicalMachineInteraction.java | 32 ++- .../gameserver/zone/EcologicalMachine.java | 7 +- .../gameserver/zone/MachineManager.java | 201 +++++++++++++++++ .../java/brainwine/gameserver/zone/Zone.java | 204 ++++-------------- .../gameserver/zone/ZoneConfigFile.java | 7 + .../gameserver/zone/ZoneManager.java | 3 + .../gen/tasks/StructureGeneratorTask.java | 4 +- 9 files changed, 322 insertions(+), 182 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/zone/MachineManager.java diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/EcoCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/EcoCommand.java index ba1119cd..85a7e75e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/EcoCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/EcoCommand.java @@ -3,7 +3,7 @@ import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; import java.util.Arrays; -import java.util.List; +import java.util.Collection; import brainwine.gameserver.annotations.CommandInfo; import brainwine.gameserver.commands.Command; @@ -48,13 +48,12 @@ public void execute(CommandExecutor executor, String[] args) { if(action.equals("add")) { if(part == null) { - machine.getParts().forEach(zone::discoverMachinePart); - zone.discoverMachinePart(machine.getBase()); + machine.getParts().forEach(zone::addMachinePart); player.notify(String.format("Added all %s components.", machine.getId()), SYSTEM); return; } - if(zone.discoverMachinePart(part)) { + if(zone.addMachinePart(part)) { player.notify(String.format("Added %s component '%s'", machine.getId(), part.getId()), SYSTEM); return; } @@ -65,13 +64,12 @@ public void execute(CommandExecutor executor, String[] args) { if(action.equals("remove")) { if(part == null) { - machine.getParts().forEach(x -> zone.removeDiscoveredPart(machine, x)); - zone.removeDiscoveredPart(machine, machine.getBase()); + machine.getParts().forEach(zone::removeMachinePart); player.notify(String.format("Removed all %s components.", machine.getId()), SYSTEM); return; } - if(zone.removeDiscoveredPart(machine, part)) { + if(zone.removeMachinePart(part)) { player.notify(String.format("Removed %s component '%s'", machine.getId(), part.getId()), SYSTEM); return; } @@ -84,8 +82,8 @@ public void execute(CommandExecutor executor, String[] args) { return; } - List parts = zone.getDiscoveredParts(machine); - player.notify(String.format("Discovered %s/%s %s components%s", parts.size(), machine.getPartCount() + 1, machine.getId(), parts.isEmpty() ? "." : ": " + parts), SYSTEM); + Collection parts = zone.getDiscoveredParts(machine); + player.notify(String.format("Discovered %s/%s %s components%s", parts.size(), machine.getPartCount(), machine.getId(), parts.isEmpty() ? "." : ": " + parts), SYSTEM); } @Override diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java index d88c35c8..0801b5e5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java @@ -1,13 +1,18 @@ package brainwine.gameserver.item.interactions; +import java.util.stream.Stream; + import brainwine.gameserver.GameServer; import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.player.NotificationType; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; import brainwine.gameserver.loot.Loot; +import brainwine.gameserver.util.MapHelper; +import brainwine.gameserver.zone.EcologicalMachine; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; @@ -77,11 +82,28 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i } else { player.notify("No eligible loot could be found for this container."); } - } else { - if(zone.discoverMachinePart(player, ItemRegistry.getItem(specialItem))) { - metaBlock.removeProperty("$"); + } else { + Item machinePart = ItemRegistry.getItem(specialItem); + + if(zone.addMachinePart(machinePart)) { + EcologicalMachine machine = EcologicalMachine.fromPart(machinePart); + String machineName = machine.toString().toLowerCase(); + String determiner = Stream.of("a", "e", "i", "o", "u").filter(machineName::startsWith).findFirst().isPresent() ? "an" : "a"; + String text = String.format("You discovered %s %s component!", determiner, machineName); + + if(player.isV3()) { + player.notify(text, NotificationType.ACCOMPLISHMENT); + } else { + Object message = MapHelper.map(String.class, String.class, + "t", text, + "i", machinePart.getId()); + player.notify(message, NotificationType.ACCOMPLISHMENT); + } + + player.notifyPeers(String.format("%s discovered %s %s component.", player.getName(), determiner, machineName), NotificationType.PEER_ACCOMPLISHMENT); + player.getStatistics().trackDiscovery(machinePart); } else { - // TODO notify player + // TODO how should we handle this...? } } } diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/EcologicalMachineInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/EcologicalMachineInteraction.java index 8185ced1..f28f5c01 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/EcologicalMachineInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/EcologicalMachineInteraction.java @@ -1,6 +1,7 @@ package brainwine.gameserver.item.interactions; import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.player.NotificationType; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; @@ -16,6 +17,8 @@ public EcologicalMachineInteraction(EcologicalMachine machine) { this.machine = machine; } + public abstract void interact(Zone zone, Player player, int x, int y); + @Override public final void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, Object config, Object[] data) { @@ -27,13 +30,36 @@ public final void interact(Zone zone, Entity entity, int x, int y, Layer layer, Player player = (Player)entity; // Do nothing if the machine cannot be used yet. - if(!zone.canUseMachine(machine, player, x, y)) { + if(!canUseMachine(zone, player, x, y, metaBlock)) { return; } // Handle interaction interact(zone, player, x, y); } - - public abstract void interact(Zone zone, Player player, int x, int y); + + private boolean canUseMachine(Zone zone, Player player, int x, int y, MetaBlock metaBlock) { + // Check if machine is already active + if(metaBlock.getBooleanProperty("activated")) { + return true; + } + + int totalParts = machine.getPartCount(); + int foundParts = zone.getDiscoveredParts(machine).size(); + + // Check if parts have been discovered + if(foundParts < totalParts) { + int remainingParts = totalParts - foundParts; + player.notify(String.format("%s part%s of the %s still need%s to be found.", + remainingParts, remainingParts == 1 ? "" : "s", machine.getId(), remainingParts == 1 ? "s" : "")); + return false; + } + + // Activate the machine! + metaBlock.setProperty("activated", true); + zone.updateBlock(x, y, Layer.FRONT, machine.getBase(), 2, null, metaBlock.getMetadata()); // TODO + player.notify(String.format("You activated the %s!", machine.getId()), NotificationType.ACCOMPLISHMENT); + player.notifyPeers(String.format("%s activated the %s!", player.getName(), machine.getId()), NotificationType.PEER_ACCOMPLISHMENT); + return false; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/EcologicalMachine.java b/gameserver/src/main/java/brainwine/gameserver/zone/EcologicalMachine.java index 7c7419cb..ffbc8450 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/EcologicalMachine.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/EcologicalMachine.java @@ -1,5 +1,6 @@ package brainwine.gameserver.zone; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -72,7 +73,7 @@ public static EcologicalMachine fromBase(Item base) { } public static EcologicalMachine fromPart(Item part) { - return Stream.of(values()).filter(x -> x.isMachinePart(part)).findFirst().orElse(fromBase(part)); + return Stream.of(values()).filter(x -> x.isMachinePart(part)).findFirst().orElse(null); } public static boolean isMachine(Item item) { @@ -93,7 +94,7 @@ public Item getBase() { } public boolean isMachinePart(Item item) { - return item.hasId(base) || parts.contains(item.getId()); + return parts.contains(item.getId()); } public int getPartCount() { @@ -101,6 +102,6 @@ public int getPartCount() { } public List getParts() { - return parts.stream().map(ItemRegistry::getItem).collect(Collectors.toList()); + return parts.stream().map(ItemRegistry::getItem).collect(Collectors.toCollection(ArrayList::new)); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/MachineManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/MachineManager.java new file mode 100644 index 00000000..d40d9505 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/zone/MachineManager.java @@ -0,0 +1,201 @@ +package brainwine.gameserver.zone; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.server.messages.ZoneStatusMessage; +import brainwine.gameserver.util.MapHelper; + +/** + * Manages ecological machines in a zone. + */ +public class MachineManager { + + public static final float PURIFICATION_TIME_SECONDS = 60.0F * 60.0F * 24.0F * 3.0F; // 72 hours (3 days) + private final Map> discoveredParts = new HashMap<>(); + private final Map machineBlocks = new HashMap<>(); + private final Zone zone; + + public MachineManager(Zone zone) { + this.zone = zone; + } + + /** + * Decreases the zone's acidity if the purifier has been activated. + */ + public void updatePurifier(float deltaTime) { + // Do nothing if purifier isn't active + if(!isMachineActive(EcologicalMachine.PURIFIER)) { + return; + } + + // Reduce acidity + float amount = deltaTime / PURIFICATION_TIME_SECONDS; + zone.setAcidity(Math.max(0.0F, zone.getAcidity() - amount)); + } + + /** + * @return {@code true} if the specified machine has been activated, otherwise {@code false}. + */ + public boolean isMachineActive(EcologicalMachine machine) { + return machineBlocks.values().stream() + .filter(block -> block.getItem() == machine.getBase() && block.getBooleanProperty("activated")) + .findFirst().isPresent(); + } + + /** + * This will show the number of discovered components on the minimap + * as well as update the machine sprite on V2 clients. + */ + public void sendMachineStatus(Player player) { + // V3 unfortunately doesn't seem to track machine progress + if(player.isV3()) { + return; + } + + // Create client data + Map data = discoveredParts.entrySet().stream() + .collect(Collectors.toMap( + entry -> entry.getKey().getClientId(), // Map machine instance to client ID + entry -> entry.getValue().stream() + .map(Item::getCode) // Map item instance to item code + .collect(Collectors.toCollection(ArrayList::new)))); + + // Sneakily add base parts of active machines to client data + List activeMachines = Stream.of(EcologicalMachine.values()) + .filter(this::isMachineActive) + .collect(Collectors.toList()); + activeMachines.forEach(machine -> MapHelper.appendList(data, machine.getClientId(), machine.getBase().getCode())); + + // Send data + player.sendMessage(new ZoneStatusMessage(MapHelper.map("machines", data))); + } + + /** + * Sync machine status with all players in the zone. + */ + private void updateMachineStatus(EcologicalMachine machine) { + // Find all machines of this type in the zone + List metaBlocks = machineBlocks.values().stream() + .filter(block -> block.getItem() == machine.getBase()) + .collect(Collectors.toList()); + + // Get list of discovered parts and transform to client data + List parts = discoveredParts.getOrDefault(machine, Collections.emptyList()).stream() + .filter(machine::isMachinePart) + .map(Item::getCode) + .collect(Collectors.toList()); + parts.add(machine.getBase().getCode()); + + // Update the machine sprite for V3 clients + for(MetaBlock metaBlock : metaBlocks) { + metaBlock.setProperty("spr", parts); + zone.sendBlockMetaUpdate(metaBlock); + } + + // Send machine status update to players + for(Player player : zone.getPlayers()) { + sendMachineStatus(player); + } + } + + // TODO rethink + public boolean addMachinePart(Item part) { + EcologicalMachine machine = EcologicalMachine.fromPart(part); + + // Do nothing if machine doesn't exist + if(machine == null) { + return false; + } + + List parts = discoveredParts.computeIfAbsent(machine, x -> new ArrayList<>()); + + // Do nothing if part has already been discovered + if(parts.contains(part)) { + return false; + } + + // Update machine status + parts.add(part); + updateMachineStatus(machine); + return true; + } + + // TODO rethink + public boolean removeMachinePart(Item part) { + EcologicalMachine machine = EcologicalMachine.fromPart(part); + + // Do nothing if machine doesn't exist + if(machine == null) { + return false; + } + + List parts = discoveredParts.get(machine); + + if(parts != null) { + parts.remove(part); + + // Remove key if there are no parts left + if(parts.isEmpty()) { + discoveredParts.remove(machine); + } + + // Update machine status + updateMachineStatus(machine); + return true; + } + + return false; + } + + /** + * @return An immutable view of the discovered parts for the specified machine. + */ + public Collection getDiscoveredParts(EcologicalMachine machine) { + return Collections.unmodifiableCollection(discoveredParts.getOrDefault(machine, Collections.emptyList())); + } + + /** + * @return An immutable view of all of the discovered machine parts. + */ + public Map> getDiscoveredParts() { + return Collections.unmodifiableMap(discoveredParts); + } + + protected void loadData(ZoneConfigFile config) { + // Filter out invalid + Map> discoveredParts = config.getDiscoveredParts().entrySet().stream() + .collect(Collectors.toMap( + Entry::getKey, + entry -> entry.getValue().stream() + .filter(item -> entry.getKey().isMachinePart(item)) + .collect(Collectors.toCollection(ArrayList::new)))); + this.discoveredParts.putAll(discoveredParts); + } + + protected void indexMetaBlock(int index, MetaBlock metaBlock) { + EcologicalMachine machine = EcologicalMachine.fromBase(metaBlock.getItem()); + + if(machine != null) { + machineBlocks.put(index, metaBlock); + updateMachineStatus(machine); + } + } + + protected void unindexMetaBlock(int index) { + MetaBlock metaBlock = machineBlocks.remove(index); + + if(metaBlock != null) { + updateMachineStatus(EcologicalMachine.fromBase(metaBlock.getItem())); + } + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 643f7a44..4c2e9098 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -53,6 +53,9 @@ import brainwine.gameserver.util.SimplexNoise; import brainwine.gameserver.util.Vector2i; +/** + * TODO Zone class is getting kinda big. I want to split it into more smaller classes to make it more manageable. + */ public class Zone { public static final int DEFAULT_CHUNK_WIDTH = 20; @@ -80,6 +83,7 @@ public class Zone { private final WeatherManager weatherManager = new WeatherManager(); private final EntityManager entityManager = new EntityManager(this); private final LiquidManager liquidManager = new LiquidManager(this); + private final MachineManager machineManager = new MachineManager(this); private final List blockChanges = new ArrayList<>(); private final List> blockTimers = new ArrayList<>(); private final Set pendingSunlight = new HashSet<>(); @@ -88,8 +92,6 @@ public class Zone { private final Map globalMetaBlocks = new HashMap<>(); private final Map fieldBlocks = new HashMap<>(); private final Map damageFieldBlocks = new HashMap<>(); - private final Map ecologicalMachineBlocks = new HashMap<>(); - private final Map> discoveredParts = new HashMap<>(); private long lastStatusUpdate = System.currentTimeMillis(); private int ticksElapsed; @@ -104,9 +106,9 @@ protected Zone(String documentId, ZoneConfigFile config, ZoneDataFile data) { this.depths = depths != null && depths.length == 3 ? depths : this.depths; this.chunksExplored = chunksExplored != null && chunksExplored.length == getChunkCount() ? chunksExplored : this.chunksExplored; steamManager.setData(data.getSteamData()); + machineManager.loadData(config); pendingSunlight.addAll(data.getPendingSunlight()); acidity = biome == Biome.ARCTIC || biome == Biome.SPACE ? 0 : config.getAcidity(); - discoveredParts.putAll(config.getDiscoveredParts()); creationDate = config.getCreationDate(); } @@ -140,6 +142,7 @@ public void tick(float deltaTime) { entityManager.tick(deltaTime); liquidManager.tick(deltaTime); steamManager.tick(deltaTime); + simulate(deltaTime); // One full cycle = 1200 seconds = 20 minutes time += deltaTime * (1.0F / 1200.0F); @@ -207,6 +210,13 @@ public void tick(float deltaTime) { ticksElapsed++; } + /** + * Simulate happenings that take longer periods of time + */ + protected void simulate(float deltaTime) { + machineManager.updatePurifier(deltaTime); + } + /** * Sends a message to each player in this zone. * @@ -879,140 +889,6 @@ public boolean isDungeonIntact(String id) { return dungeons.containsKey(id); } - /** - * This will show the number of discovered components on the minimap - * as well as update the machine sprite on V2 clients. - */ - public void sendMachineStatus(Player player) { - // V3 unfortunately doesn't seem to track machine progress - if(player.isV3()) { - return; - } - - // Get list of ecological machine types that exist in the zone - // TODO this isn't necessary: it originally only showed minimap status of machines that have had components discovered. - List machines = ecologicalMachineBlocks.values().stream() - .map(MetaBlock::getItem) - .distinct().map(EcologicalMachine::fromBase) - .collect(Collectors.toList()); - - // Create client data - Map data = machines.stream() - .collect(Collectors.toMap( - x -> String.valueOf(x.getClientId()), // Map machine instance to client ID - x -> discoveredParts.getOrDefault(x, Collections.emptyList()).stream() - .map(Item::getCode) // Map item instance to item code - .collect(Collectors.toList()))); - - // Send data - player.sendMessage(new ZoneStatusMessage(MapHelper.map("machines", data))); - } - - /** - * Sync machine status with all players in the zone. - */ - private void updateMachineStatus(EcologicalMachine machine) { - // Find all machines of this type in the zone - List metaBlocks = ecologicalMachineBlocks.values().stream() - .filter(x -> x.getItem() == machine.getBase()) - .collect(Collectors.toList()); - - // Get list of discovered parts and transform to client data - List parts = discoveredParts.getOrDefault(machine, Collections.emptyList()).stream() - .filter(machine::isMachinePart) - .map(Item::getCode) - .collect(Collectors.toList()); - parts.add(machine.getBase().getCode()); - - // Update the machine sprite for V3 clients - for(MetaBlock metaBlock : metaBlocks) { - metaBlock.setProperty("spr", parts); - sendBlockMetaUpdate(metaBlock); - } - - // Send machine status update to players - for(Player player : getPlayers()) { - sendMachineStatus(player); - } - } - - public boolean discoverMachinePart(Item part) { - return discoverMachinePart(null, part); - } - - public boolean discoverMachinePart(Player player, Item part) { - EcologicalMachine machine = EcologicalMachine.fromPart(part); - - // Do nothing if machine doesn't exist - if(machine == null) { - return false; - } - - List parts = discoveredParts.computeIfAbsent(machine, x -> new ArrayList<>()); - - // Do nothing if part has already been discovered - if(parts.contains(part)) { - return false; - } - - // Send notifications if part was discovered by a player - // TODO machine base shouldn't do this - if(player != null) { - String machineName = machine.toString().toLowerCase(); - String determiner = Stream.of("a", "e", "i", "o", "u").filter(x -> machineName.startsWith(x)).findFirst().isPresent() ? "an" : "a"; - String text = String.format("You discovered %s %s component!", determiner, machineName); - - if(player.isV3()) { - player.notify(text, NotificationType.ACCOMPLISHMENT); - } else { - Object message = MapHelper.map(String.class, String.class, - "t", text, - "i", part.getId()); - player.notify(message, NotificationType.ACCOMPLISHMENT); - } - - player.notifyPeers(String.format("%s discovered %s %s component.", player.getName(), determiner, machineName), NotificationType.PEER_ACCOMPLISHMENT); - player.getStatistics().trackDiscovery(part); - } - - // Update machine status - parts.add(part); - updateMachineStatus(machine); - return true; - } - - public boolean canUseMachine(EcologicalMachine machine, Player player, int x, int y) { - // Check if chunk is loaded - if(!isChunkLoaded(x, y)) { - return false; - } - - int mod = getBlock(x, y).getFrontMod(); - - // Check if machine is already active - if(mod != 0) { - return true; - } - - int totalParts = machine.getPartCount(); - int foundParts = discoveredParts.getOrDefault(machine, Collections.emptyList()).size(); - - // Check if parts have been discovered - if(foundParts < totalParts) { - int remainingParts = totalParts - foundParts; - player.notify(String.format("%s part%s of the %s still need%s to be found.", - remainingParts, remainingParts == 1 ? "" : "s", machine.getId(), remainingParts == 1 ? "s" : "")); - return false; - } - - // Discover the final part and activate the machine! - discoverMachinePart(player, machine.getBase()); - updateBlock(x, y, Layer.FRONT, machine.getBase(), 1, null, getMetaBlock(x, y).getMetadata()); // TODO - player.notify(String.format("You activated the %s!", machine.getId()), NotificationType.ACCOMPLISHMENT); - player.notifyPeers(String.format("%s activated the %s!", player.getName(), machine.getId()), NotificationType.PEER_ACCOMPLISHMENT); - return false; - } - public boolean digBlock(int x, int y) { if(!areCoordinatesInBounds(x, y)) { return false; @@ -1248,12 +1124,7 @@ private void indexMetaBlock(int index, MetaBlock block) { damageFieldBlocks.put(index, block); } - EcologicalMachine machine = EcologicalMachine.fromBase(item); - - if(machine != null) { - ecologicalMachineBlocks.put(index, block); - updateMachineStatus(machine); // TODO this updates ALL machines of this type which isn't really an issue but still kinda scuffed! - } + machineManager.indexMetaBlock(index, block); } private void unindexMetaBlock(int index) { @@ -1261,7 +1132,7 @@ private void unindexMetaBlock(int index) { globalMetaBlocks.remove(index); fieldBlocks.remove(index); damageFieldBlocks.remove(index); - ecologicalMachineBlocks.remove(index); + machineManager.unindexMetaBlock(index); } protected void setMetaBlocks(List metaBlocks) { @@ -1412,6 +1283,34 @@ public int settleLiquids() { return liquidManager.settleLiquids(); } + public boolean isMachineActive(EcologicalMachine machine) { + return machineManager.isMachineActive(machine); + } + + public void sendMachineStatus(Player player) { + machineManager.sendMachineStatus(player); + } + + public boolean addMachinePart(Item part) { + return machineManager.addMachinePart(part); + } + + public boolean removeMachinePart(Item part) { + return machineManager.removeMachinePart(part); + } + + public Collection getDiscoveredParts(EcologicalMachine machine) { + return machineManager.getDiscoveredParts(machine); + } + + public Map> getDiscoveredParts() { + return machineManager.getDiscoveredParts(); + } + + public MachineManager getMachineManager() { + return machineManager; + } + /** * @return The specified coordinates in a player-readable format * For example, {@code x: 200 y: 300} in a plain biome becomes {@code 800 west, 100 below} @@ -1710,23 +1609,6 @@ public float getAcidity() { return acidity; } - public boolean removeDiscoveredPart(EcologicalMachine machine, Item part) { - if(discoveredParts.containsKey(machine) && discoveredParts.get(machine).remove(part)) { - updateMachineStatus(machine); - return true; - } - - return false; - } - - public List getDiscoveredParts(EcologicalMachine machine) { - return discoveredParts.getOrDefault(machine, Collections.emptyList()); - } - - public Map> getDiscoveredParts() { - return Collections.unmodifiableMap(discoveredParts); - } - public OffsetDateTime getCreationDate() { return creationDate; } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java index 7a88a00e..0bbe0080 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java @@ -37,6 +37,9 @@ public class ZoneConfigFile { @JsonSetter(nulls = Nulls.SKIP) private OffsetDateTime creationDate = OffsetDateTime.now(); + @JsonSetter(nulls = Nulls.SKIP) + private OffsetDateTime lastActiveDate = OffsetDateTime.now(); + public ZoneConfigFile(Zone zone) { this(zone.getName(), zone.getBiome(), zone.getWidth(), zone.getHeight(), zone.getAcidity(), zone.getDiscoveredParts(), zone.getCreationDate()); } @@ -88,4 +91,8 @@ public Map> getDiscoveredParts() { public OffsetDateTime getCreationDate() { return creationDate; } + + public OffsetDateTime getLastActiveDate() { + return lastActiveDate; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java index 4d0ed756..61666851 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java @@ -5,6 +5,8 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.time.OffsetDateTime; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; @@ -149,6 +151,7 @@ private void loadZone(File file) { zone.spawnPersistentNpcs(JsonHelper.readList(charactersFile, NpcData.class)); } + zone.simulate(ChronoUnit.SECONDS.between(config.getLastActiveDate(), OffsetDateTime.now())); addZone(zone); } catch (Exception e) { logger.error(SERVER_MARKER, "Zone load failure. id: {}", id, e); diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java index dc64ae0a..30a17b81 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java @@ -198,9 +198,9 @@ private void placeComponentChests(GeneratorContext ctx, List containe iterator.remove(); } - // If there are still parts left but no containers, just discover 'em. + // If there are still parts left but no containers, just add 'em. for(Item part : parts) { - ctx.getZone().discoverMachinePart(part); + ctx.getZone().addMachinePart(part); } } From aaeb8acb0a6b3c004ae7a7d750658cc806b21367 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Wed, 8 May 2024 23:55:25 +0200 Subject: [PATCH 074/176] Forgot something --- .../gameserver/item/interactions/ContainerInteraction.java | 1 + 1 file changed, 1 insertion(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java index 0801b5e5..f7d640f2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java @@ -102,6 +102,7 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i player.notifyPeers(String.format("%s discovered %s %s component.", player.getName(), determiner, machineName), NotificationType.PEER_ACCOMPLISHMENT); player.getStatistics().trackDiscovery(machinePart); + metaBlock.removeProperty("$"); } else { // TODO how should we handle this...? } From f7f201d8f8347fa14e034d9d0f0c34a3403b28d6 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 9 May 2024 18:54:23 +0200 Subject: [PATCH 075/176] Change some comments --- .../main/java/brainwine/gameserver/zone/MachineManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/MachineManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/MachineManager.java index d40d9505..a86ecf0d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/MachineManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/MachineManager.java @@ -63,9 +63,9 @@ public void sendMachineStatus(Player player) { } // Create client data - Map data = discoveredParts.entrySet().stream() + Map> data = discoveredParts.entrySet().stream() .collect(Collectors.toMap( - entry -> entry.getKey().getClientId(), // Map machine instance to client ID + entry -> entry.getKey().getClientId(), // Map machine type to client ID entry -> entry.getValue().stream() .map(Item::getCode) // Map item instance to item code .collect(Collectors.toCollection(ArrayList::new)))); @@ -172,7 +172,7 @@ public Map> getDiscoveredParts() { } protected void loadData(ZoneConfigFile config) { - // Filter out invalid + // Filter out invalid parts Map> discoveredParts = config.getDiscoveredParts().entrySet().stream() .collect(Collectors.toMap( Entry::getKey, From 5f0dfae72f30a85e48b00ce9dba447ad11877022 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Fri, 10 May 2024 15:24:44 +0200 Subject: [PATCH 076/176] Implement placed fieldability --- .../gameserver/server/requests/BlockMineRequest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index fbd51cbb..5aadf363 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -62,8 +62,10 @@ public void process(Player player) { return; } - // TODO block ownership & 'placed' fieldability - if(!player.isGodMode() && !digging && item.getFieldability() == Fieldability.TRUE && zone.isBlockProtected(x, y, player)) { + Fieldability fieldability = item.getFieldability(); + boolean fieldable = fieldability == Fieldability.TRUE || (fieldability == Fieldability.PLACED && !block.isNatural()); + + if(!player.isGodMode() && !digging && fieldable && zone.isBlockProtected(x, y, player)) { fail(player, "This block is protected."); return; } From 37cc6bac55240596bdc2f4d4ce46787d565da8fd Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 14 May 2024 12:27:15 +0200 Subject: [PATCH 077/176] Execute unknown `AdminRequest` keys as console commands --- .../server/requests/AdminRequest.java | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/AdminRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/AdminRequest.java index 157aeb6a..651b8869 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/AdminRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/AdminRequest.java @@ -1,6 +1,11 @@ package brainwine.gameserver.server.requests; +import java.util.Collection; +import java.util.stream.Collectors; + +import brainwine.gameserver.annotations.OptionalField; import brainwine.gameserver.annotations.RequestInfo; +import brainwine.gameserver.commands.CommandManager; import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.server.PlayerRequest; @@ -8,6 +13,8 @@ public class AdminRequest extends PlayerRequest { public String key; + + @OptionalField public Object data; @Override @@ -17,8 +24,24 @@ public void process(Player player) { } switch(key) { - case "god": player.setGodMode(data == null || data.equals(1)); - default: break; + case "god": + player.setGodMode(data == null || data.equals(1)); + break; + case "admin": + // This is a client-sided fuck-up + if(player.isV3()) { + key = "grow"; + } + default: + // Delegate request to the command manager + if(data == null) { + CommandManager.executeCommand(player, String.format("/%s", key)); + } else { + String parameters = data instanceof Collection ? String.join(" ", + ((Collection)data).stream().map(String::valueOf).collect(Collectors.toList())) : String.valueOf(data); + CommandManager.executeCommand(player, String.format("/%s %s", key, parameters)); + } + break; } } } From 8c7ae35bef564d8d4cde1e67a8f0ea4f6949b234 Mon Sep 17 00:00:00 2001 From: Kuroppoi Date: Thu, 16 May 2024 19:56:26 +0200 Subject: [PATCH 078/176] Implement plant growth (#51) --- .../java/brainwine/gameserver/GameServer.java | 2 + .../commands/admin/GrowCommand.java | 46 ++++ .../java/brainwine/gameserver/item/Item.java | 11 + .../server/requests/BlockMineRequest.java | 12 +- .../gameserver/util/ResourceUtils.java | 23 ++ .../brainwine/gameserver/zone/Growable.java | 31 +++ .../gameserver/zone/GrowthManager.java | 154 ++++++++++++++ .../gameserver/zone/WeatherManager.java | 11 +- .../java/brainwine/gameserver/zone/Zone.java | 32 ++- .../src/main/resources/defaults/growth.json | 196 ++++++++++++++++++ .../java/brainwine/shared/JsonHelper.java | 11 +- 11 files changed, 521 insertions(+), 8 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/commands/admin/GrowCommand.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/zone/Growable.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/zone/GrowthManager.java create mode 100644 gameserver/src/main/resources/defaults/growth.json diff --git a/gameserver/src/main/java/brainwine/gameserver/GameServer.java b/gameserver/src/main/java/brainwine/gameserver/GameServer.java index a115f66c..4585e0e0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/GameServer.java +++ b/gameserver/src/main/java/brainwine/gameserver/GameServer.java @@ -19,6 +19,7 @@ import brainwine.gameserver.server.NetworkRegistry; import brainwine.gameserver.server.Server; import brainwine.gameserver.zone.EntityManager; +import brainwine.gameserver.zone.GrowthManager; import brainwine.gameserver.zone.ZoneManager; import brainwine.gameserver.zone.gen.ZoneGenerator; @@ -48,6 +49,7 @@ public GameServer() { AchievementManager.loadAchievements(); EntityRegistry.init(); EntityManager.loadEntitySpawns(); + GrowthManager.loadGrowthData(); lootManager = new LootManager(); prefabManager = new PrefabManager(); ZoneGenerator.init(); diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/GrowCommand.java b/gameserver/src/main/java/brainwine/gameserver/commands/admin/GrowCommand.java new file mode 100644 index 00000000..87760bde --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/commands/admin/GrowCommand.java @@ -0,0 +1,46 @@ +package brainwine.gameserver.commands.admin; + +import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; + +import brainwine.gameserver.annotations.CommandInfo; +import brainwine.gameserver.commands.Command; +import brainwine.gameserver.commands.CommandExecutor; +import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.zone.GrowthManager; +import brainwine.gameserver.zone.Zone; + +@CommandInfo(name = "grow", description = "Simulate plant growth in all loaded chunks.") +public class GrowCommand extends Command { + + @Override + public void execute(CommandExecutor executor, String[] args) { + if(args.length == 0) { + executor.notify(String.format("Usage: %s", getUsage(executor)), SYSTEM); + return; + } + + int cycles = 0; + + try { + cycles = Math.min(GrowthManager.MAX_RAIN_CYCLES, Integer.parseInt(args[0])); + } catch(NumberFormatException e) { + executor.notify("Rain cycles must be a valid number.", SYSTEM); + return; + } + + Player player = (Player)executor; + Zone zone = player.getZone(); + zone.updateGrowables(cycles); + player.notify(String.format("Simulated %s rain cycles.", cycles), SYSTEM); + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/grow "; + } + + @Override + public boolean canExecute(CommandExecutor executor) { + return executor.isAdmin() && executor instanceof Player; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/Item.java b/gameserver/src/main/java/brainwine/gameserver/item/Item.java index e1a1c777..ce497e1e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/Item.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/Item.java @@ -132,6 +132,9 @@ public class Item { @JsonProperty("decay inventory") private LazyItemGetter decayInventoryItem; + @JsonProperty("mod_inventory") + private Pair modInventoryItem; + @JsonProperty("crafting quantity") private int craftingQuantity = 1; @@ -460,6 +463,14 @@ public Item getDecayInventoryItem() { return decayInventoryItem == null ? this : decayInventoryItem.get(); } + public boolean hasModInventoryItem() { + return modInventoryItem != null; + } + + public Item getModInventoryItem(int mod) { + return modInventoryItem == null ? this : mod >= modInventoryItem.getFirst() ? modInventoryItem.getLast().get() : Item.AIR; + } + public String[] getLootCategories() { return lootCategories; } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index 5aadf363..c600bc9f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -168,7 +168,17 @@ public void process(Player player) { zone.spawnEntity(item.getEntitySpawns().next(), x, y); } - Item inventoryItem = item.getMod() == ModType.DECAY && block.getMod(layer) > 0 ? item.getDecayInventoryItem() : item.getInventoryItem(); + // Determine inventory item + Item inventoryItem; + + if(item.getMod() == ModType.DECAY && block.getMod(layer) > 0) { + inventoryItem = item.getDecayInventoryItem(); + } else if(item.hasModInventoryItem()) { + inventoryItem = item.getModInventoryItem(block.getMod(layer)); + } else { + inventoryItem = item.getInventoryItem(); + } + int quantity = 1; player.getStatistics().trackItemMined(item); zone.updateBlock(x, y, layer, 0, 0, player); diff --git a/gameserver/src/main/java/brainwine/gameserver/util/ResourceUtils.java b/gameserver/src/main/java/brainwine/gameserver/util/ResourceUtils.java index ee6609a5..3c44ce30 100644 --- a/gameserver/src/main/java/brainwine/gameserver/util/ResourceUtils.java +++ b/gameserver/src/main/java/brainwine/gameserver/util/ResourceUtils.java @@ -1,6 +1,9 @@ package brainwine.gameserver.util; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.util.Set; @@ -15,10 +18,30 @@ public class ResourceUtils { private static final Logger logger = LogManager.getLogger(); + public static InputStream getOverridableResource(String name) throws IOException { + File file = new File(name); + + if(!file.exists()) { + return ResourceUtils.class.getResourceAsStream(String.format("/defaults/%s", name)); + } + + return new FileInputStream(file); + } + + public static Set getResourceNames(String directory) { + Reflections reflections = new Reflections(new ConfigurationBuilder() + .setUrls(ClasspathHelper.forResource("defaults")) + .setInputsFilter(x -> x.matches(String.format("defaults/%s.*", directory))) + .setScanners(Scanners.Resources)); + return reflections.getResources(".*"); + } + + @Deprecated public static void copyDefaults(String path) { copyDefaults(path, false); } + @Deprecated public static void copyDefaults(String path, boolean force) { try { File file = new File(path); diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Growable.java b/gameserver/src/main/java/brainwine/gameserver/zone/Growable.java new file mode 100644 index 00000000..c7cddc66 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Growable.java @@ -0,0 +1,31 @@ +package brainwine.gameserver.zone; + +import java.beans.ConstructorProperties; + +import brainwine.gameserver.item.Item; + +public class Growable { + + private final int maxMod; + private final double chance; + private final Item replaceSource; + + @ConstructorProperties({"max_mod", "chance", "replace_source"}) + public Growable(int maxMod, double chance, Item replaceSource) { + this.maxMod = maxMod; + this.chance = chance; + this.replaceSource = replaceSource; + } + + public int getMaxMod() { + return maxMod; + } + + public double getChance() { + return chance; + } + + public Item getReplaceSource() { + return replaceSource; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/GrowthManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/GrowthManager.java new file mode 100644 index 00000000..9333ebf8 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/zone/GrowthManager.java @@ -0,0 +1,154 @@ +package brainwine.gameserver.zone; + +import static brainwine.shared.LogMarkers.SERVER_MARKER; + +import java.io.InputStream; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.fasterxml.jackson.core.type.TypeReference; + +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.Layer; +import brainwine.gameserver.util.ResourceUtils; +import brainwine.gameserver.util.WeightedMap; +import brainwine.shared.JsonHelper; + +/** + * Manages plant growth in a zone. + */ +public class GrowthManager { + + public static final int MAX_RAIN_CYCLES = 500; // Maximum number of rain cycles that are permitted in a single growth update + private static final Logger logger = LogManager.getLogger(); + private static final Map growables = new HashMap<>(); + private static final Map>> sourcesByBiome = new HashMap<>(); + private final Set sourceIndices = new HashSet<>(); + private final Map> sources; + private final Zone zone; + + public GrowthManager(Zone zone) { + this.sources = sourcesByBiome.getOrDefault(zone.getBiome(), Collections.emptyMap()); + this.zone = zone; + } + + /** + * Loads growth data configuration + */ + public static void loadGrowthData() { + growables.clear(); + sourcesByBiome.clear(); + + try(InputStream inputStream = ResourceUtils.getOverridableResource("growth.json")) { + Map data = JsonHelper.readValue(inputStream, new TypeReference>(){}); + growables.putAll(JsonHelper.readValue(data.get("growables"), new TypeReference>(){})); + sourcesByBiome.putAll(JsonHelper.readValue(data.get("sources"), new TypeReference>>>(){})); + } catch(Exception e) { + logger.error(SERVER_MARKER, "Could not load growth data", e); + } + } + + /** + * Calls {@link #updateGrowables(int, Collection)} where {@code sourceIndices} is the currently indexed growables. + */ + public void updateGrowables(int rainCycles) { + updateGrowables(rainCycles, sourceIndices); + } + + /** + * Updates the specified growables {@code n} times where {@code n} is the number of rain cycles. + */ + public void updateGrowables(int rainCycles, Collection sourceIndices) { + // Do nothing if zone isn't purified + if(!zone.isPurified() && zone.getBiome() != Biome.HELL) { + return; + } + + // Do nothing if there's nothing to do... duh! + if(rainCycles < 1 || sourceIndices.isEmpty()) { + return; + } + + // Reduce overhead by reducing the number of iterations in exchange for a growth chance boost + rainCycles = Math.min(MAX_RAIN_CYCLES, rainCycles); + int growthChanceBoost = Math.min(10, rainCycles); + rainCycles /= growthChanceBoost; + + // Update growth for each rain cycle + for(int i = 0; i < rainCycles; i++) { + Iterator iterator = sourceIndices.iterator(); + + while(iterator.hasNext()) { + int index = iterator.next(); + int x = index % zone.getWidth(); + int y = index / zone.getWidth(); + + // Unindex if chunk is not loaded + if(y == 0 || !zone.isChunkLoaded(x, y)) { + iterator.remove(); + continue; + } + + // Skip if sunlight can't reach this source + if(zone.getSunlight()[x] < y) { + continue; + } + + Block sourceBlock = zone.getBlock(x, y); + Item sourceItem = sourceBlock.getFrontItem(); + + // Unindex if block is not a source + if(!sources.containsKey(sourceItem)) { + iterator.remove(); + continue; + } + + Block growableBlock = zone.getBlock(x, y - 1); + Item growableItem = growableBlock.getFrontItem(); + + // Place a random growable if block isn't occupied or try to grow it if it is a valid growable + if(growableItem.isAir()) { + growableItem = sources.get(sourceItem).next(); + + // Update block if item exists + if(growableItem != null) { + zone.updateBlock(x, y - 1, Layer.FRONT, growableItem); + } + } else if(growables.containsKey(growableItem)) { + Growable growable = growables.get(growableItem); + int mod = growableBlock.getFrontMod(); + + // Try to apply a growth stage if the plant can still grow + if(mod < growable.getMaxMod() && Math.random() < growable.getChance() * growthChanceBoost) { + zone.updateBlock(x, y - 1, Layer.FRONT, growableItem, ++mod); + + // Replace source block if max mod has been reached + if(growable.getReplaceSource() != null && mod >= growable.getMaxMod()) { + zone.updateBlock(x, y, Layer.FRONT, growable.getReplaceSource()); + } + } + } + } + } + } + + /** + * Attempts to index the block and returns {@code true} if the block's front item is a valid growth source. + */ + public boolean indexBlock(int x, int y, Item item) { + if(!sources.containsKey(item)) { + return false; + } + + sourceIndices.add(zone.getBlockIndex(x, y)); + return true; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/WeatherManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/WeatherManager.java index 5bd5c624..d4b3d72f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/WeatherManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/WeatherManager.java @@ -9,12 +9,14 @@ public class WeatherManager { private static final ThreadLocalRandom random = ThreadLocalRandom.current(); + private final Zone zone; private long rainStart; private long rainDuration; private float rainPower; private float precipitation; - public WeatherManager() { + public WeatherManager(Zone zone) { + this.zone = zone; createRandomRain(random.nextBoolean()); } @@ -22,7 +24,12 @@ public void tick(float deltaTime) { long now = System.currentTimeMillis(); if(now > rainStart + rainDuration) { - createRandomRain(rainPower > 0 ? true : false); + boolean dry = rainPower > 0; + createRandomRain(dry); + + if(dry) { + zone.updateGrowables(1); + } } float lerp = (float)(deltaTime * MathUtils.lerp(0.02F, 0.1F, (now - rainStart) / (float)rainDuration)); diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 4c2e9098..c7c35bea 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -80,7 +80,8 @@ public class Zone { private float acidity; private final ChunkManager chunkManager; private final SteamManager steamManager; - private final WeatherManager weatherManager = new WeatherManager(); + private final GrowthManager growthManager; + private final WeatherManager weatherManager = new WeatherManager(this); private final EntityManager entityManager = new EntityManager(this); private final LiquidManager liquidManager = new LiquidManager(this); private final MachineManager machineManager = new MachineManager(this); @@ -124,9 +125,10 @@ public Zone(String documentId, String name, Biome biome, int width, int height) surface = new int[width]; sunlight = new int[width]; chunksExplored = new boolean[numChunksWidth * numChunksHeight]; + acidity = biome == Biome.ARCTIC || biome == Biome.SPACE ? 0 : 1; chunkManager = new ChunkManager(this); steamManager = new SteamManager(this); - acidity = biome == Biome.ARCTIC || biome == Biome.SPACE ? 0 : 1; + growthManager = new GrowthManager(this); Arrays.fill(surface, height); Arrays.fill(sunlight, height); } @@ -1006,6 +1008,7 @@ public void updateBlock(int x, int y, Layer layer, Item item, int mod, Player ow removeBlockTimer(x, y); entityManager.trySpawnBlockEntity(x, y); steamManager.indexBlock(x, y, item); + growthManager.indexBlock(x, y, item); if(item.isWhole() && y < sunlight[x]) { sunlight[x] = y; @@ -1283,6 +1286,10 @@ public int settleLiquids() { return liquidManager.settleLiquids(); } + public void updateGrowables(int rainCycles) { + growthManager.updateGrowables(rainCycles); + } + public boolean isMachineActive(EcologicalMachine machine) { return machineManager.isMachineActive(machine); } @@ -1329,9 +1336,11 @@ public boolean areCoordinatesInBounds(int x, int y) { return x >= 0 && y >= 0 && x < width && y < height; } + // TODO move this function to ChunkManager protected void onChunkLoaded(Chunk chunk) { int chunkX = chunk.getX(); int chunkY = chunk.getY(); + List growthSourceIndices = new ArrayList<>(); for(int x = chunkX; x < chunkX + chunk.getWidth(); x++) { // Update pending sunlight @@ -1345,8 +1354,13 @@ protected void onChunkLoaded(Chunk chunk) { entityManager.trySpawnBlockEntity(x, y); Block block = chunk.getBlock(x, y); - // Index steam blocks - steamManager.indexBlock(x, y, block.getFrontItem()); + // Index front item + Item item = block.getFrontItem(); + steamManager.indexBlock(x, y, item); + + if(growthManager.indexBlock(x, y, item)) { + growthSourceIndices.add(getBlockIndex(x, y)); + } // Index liquids if(!block.getLiquidItem().isAir() && block.getLiquidMod() > 0) { @@ -1354,8 +1368,14 @@ protected void onChunkLoaded(Chunk chunk) { } } } + + // Simulate plant growth based on time passed since chunk was last loaded + int cycles = (int)((System.currentTimeMillis() - chunk.getSaveTime()) / 1200000); // One cycle per 20 minutes + growthManager.updateGrowables(cycles, growthSourceIndices); + } + // TODO move this function to ChunkManager protected void onChunkUnloaded(Chunk chunk) { // TODO is this function ever gonna be necessary? // It seems that most (if not all) thingies are unindexed automatically. @@ -1621,6 +1641,10 @@ public boolean isPopular() { return this.getPlayers().size() > 0; } + public boolean isPurified() { + return acidity < 0.05F; + } + /** * @return A {@link Map} containing all the data necessary for use in {@link ConfigurationMessage}. */ diff --git a/gameserver/src/main/resources/defaults/growth.json b/gameserver/src/main/resources/defaults/growth.json new file mode 100644 index 00000000..e277909c --- /dev/null +++ b/gameserver/src/main/resources/defaults/growth.json @@ -0,0 +1,196 @@ +{ + "growables": { + "vegetation/grass": { + "max_mod": 1, + "chance": 0.1 + }, + "vegetation/arctic-growth": { + "max_mod": 1, + "chance": 0.1 + }, + "vegetation/shrub-a": { + "max_mod": 11, + "chance": 0.1 + }, + "vegetation/shrub-b": { + "max_mod": 11, + "chance": 0.1 + }, + "vegetation/shrub-c": { + "max_mod": 14, + "chance": 0.1 + }, + "vegetation/shrub-d": { + "max_mod": 14, + "chance": 0.1 + }, + "vegetation/shrub-e": { + "max_mod": 14, + "chance": 0.1 + }, + "vegetation/shrub-f": { + "max_mod": 14, + "chance": 0.1 + }, + "vegetation/shrub-g": { + "max_mod": 14, + "chance": 0.1 + }, + "vegetation/flower-amaryllis": { + "max_mod": 4, + "chance": 0.1, + "replace_source": "ground/earth-compost" + }, + "vegetation/flower-asphodelus": { + "max_mod": 4, + "chance": 0.1, + "replace_source": "ground/earth-compost" + }, + "vegetation/flower-birds-foot-trefoil": { + "max_mod": 4, + "chance": 0.1, + "replace_source": "ground/earth-compost" + }, + "vegetation/flower-carnations": { + "max_mod": 4, + "chance": 0.1, + "replace_source": "ground/earth-compost" + }, + "vegetation/flower-coxcomb": { + "max_mod": 4, + "chance": 0.1, + "replace_source": "ground/earth-compost" + }, + "vegetation/flower-delphinium": { + "max_mod": 4, + "chance": 0.1, + "replace_source": "ground/earth-compost" + }, + "vegetation/flower-lobelia": { + "max_mod": 4, + "chance": 0.1, + "replace_source": "ground/earth-compost" + }, + "vegetation/flower-tuberose": { + "max_mod": 4, + "chance": 0.1, + "replace_source": "ground/earth-compost" + }, + "vegetation/flower-bird-of-paradise": { + "max_mod": 8, + "chance": 0.1, + "replace_source": "ground/earth-compost" + }, + "vegetation/flower-cactus": { + "max_mod": 8, + "chance": 0.1, + "replace_source": "ground/earth-compost" + }, + "vegetation/flower-echinacea": { + "max_mod": 8, + "chance": 0.1, + "replace_source": "ground/earth-compost" + }, + "vegetation/flower-sunflower": { + "max_mod": 8, + "chance": 0.1, + "replace_source": "ground/earth-compost" + }, + "vegetation/flower-trumpets": { + "max_mod": 8, + "chance": 0.1, + "replace_source": "ground/earth-compost" + }, + "vegetation/tree-bonsai": { + "max_mod": 8, + "chance": 0.1, + "replace_source": "ground/earth-compost" + }, + "vegetation/tree-hellish": { + "max_mod": 8, + "chance": 0.1, + "replace_source": "ground/earth-compost" + }, + "vegetation/pumpkin": { + "max_mod": 4, + "chance": 0.1, + "replace_source": "ground/earth-compost" + }, + "vegetation/pine-tree": { + "max_mod": 10, + "chance": 0.05 + } + }, + "sources": { + "arctic": { + "ground/earth-compost": { + "vegetation/arctic-growth": 10, + "vegetation/pine-tree": 1 + } + }, + "hell": { + "vegetation/tree-hellish-bulb": { + "vegetation/tree-hellish": 1 + } + }, + "desert": { + "vegetation/flower-cactus-bulb": { + "vegetation/flower-cactus": 1 + } + }, + "plain": { + "ground/earth-compost": { + "vegetation/grass": 120, + "vegetation/shrub-a": 33, + "vegetation/shrub-b": 34, + "vegetation/shrub-c": 33, + "vegetation/shrub-d": 25, + "vegetation/shrub-e": 20, + "vegetation/shrub-f": 15, + "vegetation/shrub-g": 15 + }, + "vegetation/flower-amaryllis-bulb": { + "vegetation/flower-amaryllis": 1 + }, + "vegetation/flower-asphodelus-bulb": { + "vegetation/flower-asphodelus": 1 + }, + "vegetation/flower-birds-foot-trefoil-bulb": { + "vegetation/flower-birds-foot-trefoil": 1 + }, + "vegetation/flower-carnations-bulb": { + "vegetation/flower-carnations": 1 + }, + "vegetation/flower-coxcomb-bulb": { + "vegetation/flower-coxcomb": 1 + }, + "vegetation/flower-delphinium-bulb": { + "vegetation/flower-delphinium": 1 + }, + "vegetation/flower-lobelia-bulb": { + "vegetation/flower-lobelia": 1 + }, + "vegetation/flower-tuberose-bulb": { + "vegetation/flower-tuberose": 1 + }, + "vegetation/flower-bird-of-paradise-bulb": { + "vegetation/flower-bird-of-paradise": 1 + }, + "vegetation/flower-echinacea-bulb": { + "vegetation/flower-echinacea": 1 + }, + "vegetation/flower-sunflower-bulb": { + "vegetation/flower-sunflower": 1 + }, + "vegetation/flower-trumpets-bulb": { + "vegetation/flower-trumpets": 1 + }, + "vegetation/tree-bonsai-bulb": { + "vegetation/tree-bonsai": 1 + }, + "vegetation/pumpkin-seeds": { + "vegetation/pumpkin": 1 + } + } + } +} diff --git a/shared/src/main/java/brainwine/shared/JsonHelper.java b/shared/src/main/java/brainwine/shared/JsonHelper.java index 83440b75..d25d5778 100644 --- a/shared/src/main/java/brainwine/shared/JsonHelper.java +++ b/shared/src/main/java/brainwine/shared/JsonHelper.java @@ -2,6 +2,7 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.util.List; import com.fasterxml.jackson.core.JsonGenerationException; @@ -37,10 +38,14 @@ public static T readValue(String string, Class type) throws JsonMappingEx return MAPPER.readValue(string, type); } - public static T readValue(File file, Class type) throws JsonParseException, JsonMappingException, IOException { + public static T readValue(File file, Class type) throws IOException { return MAPPER.readValue(file, type); } + public static T readValue(InputStream inputStream, Class type) throws IOException { + return MAPPER.readValue(inputStream, type); + } + public static T readValue(Object object, Class type) throws JsonProcessingException { return readValue(writeValueAsString(object), type); } @@ -53,6 +58,10 @@ public static T readValue(File file, TypeReference type) throws IOExcepti return MAPPER.readValue(file, type); } + public static T readValue(InputStream inputStream, TypeReference type) throws IOException { + return MAPPER.readValue(inputStream, type); + } + public static T readValue(Object object, TypeReference type) throws JsonProcessingException { return readValue(writeValueAsString(object), type); } From 0e0a82592808a650ded0102dee796a6fc775e3ad Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 16 May 2024 20:00:59 +0200 Subject: [PATCH 079/176] Change `ScavengingAchievement` to only track natural items mined --- .../achievements/ScavengingAchievement.java | 4 +- .../entity/player/PlayerStatistics.java | 41 ++++++++++++++++--- .../server/requests/BlockMineRequest.java | 8 +++- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/ScavengingAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievements/ScavengingAchievement.java index e7b7d984..a5a394d9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/ScavengingAchievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievements/ScavengingAchievement.java @@ -25,9 +25,9 @@ public int getProgress(Player player) { PlayerStatistics statistics = player.getStatistics(); if(items == null) { - return statistics.getUniqueItemsMined(); + return statistics.getUniqueItemsScavenged(); } else { - return (int)(statistics.getItemsMined().entrySet().stream() + return (int)(statistics.getItemsScavenged().entrySet().stream() .filter(entry -> entry.getValue() > 0 && items.contains(entry.getKey())) .count()); } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java index 350609f0..3115d335 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java @@ -32,6 +32,7 @@ public class PlayerStatistics { private Map itemsMined = new HashMap<>(); + private Map itemsScavenged = new HashMap<>(); private Map itemsCrafted = new HashMap<>(); private Map discoveries = new HashMap<>(); private Map kills = new HashMap<>(); @@ -62,14 +63,8 @@ protected void setPlayer(Player player) { } public void trackItemMined(Item item) { - if(!itemsMined.containsKey(item)) { - player.addExperience(150, "New item mined!"); - } - itemsMined.put(item, getItemsMined(item) + 1); - player.addExperience(item.getExperienceYield()); player.updateAchievementProgress(MiningAchievement.class); - player.updateAchievementProgress(ScavengingAchievement.class); } public void setItemsMined(Map itemsMined) { @@ -96,6 +91,40 @@ public Map getItemsMined() { return Collections.unmodifiableMap(itemsMined); } + public void trackItemScavenged(Item item) { + if(!itemsScavenged.containsKey(item)) { + player.addExperience(150, "New item mined!"); + } + + itemsScavenged.put(item, getItemsScavenged(item) + 1); + player.addExperience(item.getExperienceYield()); + player.updateAchievementProgress(ScavengingAchievement.class); + } + + public void setItemsScavenged(Map itemsScavenged) { + this.itemsScavenged = itemsScavenged; + } + + public int getTotalItemsScavenged() { + return itemsScavenged.values().stream() + .reduce(Integer::sum) + .orElse(0); + } + + public int getUniqueItemsScavenged() { + return (int)itemsScavenged.entrySet().stream() + .filter(entry -> entry.getValue() > 0) + .count(); + } + + public int getItemsScavenged(Item item) { + return itemsScavenged.getOrDefault(item, 0); + } + + public Map getItemsScavenged() { + return Collections.unmodifiableMap(itemsScavenged); + } + public void trackItemCrafted(Item item) { trackItemCrafted(item, 1); } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index c600bc9f..457f091e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -181,16 +181,22 @@ public void process(Player player) { int quantity = 1; player.getStatistics().trackItemMined(item); + + if(block.isNatural()) { + player.getStatistics().trackItemScavenged(item); + } + zone.updateBlock(x, y, layer, 0, 0, player); // Apply mining bonus if there is one if(item.hasMiningBonus()) { MiningBonus bonus = item.getMiningBonus(); + if(Math.random() < player.getMiningBonusChance(bonus)) { if(!bonus.getItem().isAir()) { inventoryItem = bonus.getItem(); } - + if(bonus.isDoubleLoot()) { quantity *= 2; } From 550e7ff6e6d5cd94c76514c4c0cbd9d952776b5d Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 16 May 2024 20:02:22 +0200 Subject: [PATCH 080/176] Add API support for sorting zones by newest --- api/src/main/java/brainwine/api/PortalService.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/brainwine/api/PortalService.java b/api/src/main/java/brainwine/api/PortalService.java index 46d0d00c..55bea25a 100644 --- a/api/src/main/java/brainwine/api/PortalService.java +++ b/api/src/main/java/brainwine/api/PortalService.java @@ -85,11 +85,14 @@ private void handleZoneSearch(Context ctx) { handleQueryParam(ctx, "sort", String.class, sort -> { switch(sort) { - case "popularity": + case "popularity": // Sort by most players first zones.removeIf(zone -> zone.getPlayerCount() == 0); zones.sort((a, b) -> Integer.compare(b.getPlayerCount(), a.getPlayerCount())); break; - case "created": + case "created": // Sort by newest first + zones.sort((a, b) -> b.getCreationDate().compareTo(a.getCreationDate())); + break; + case "development": // TODO break; } }); From c323849b771975bc24c846335d159be159852766 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 16 May 2024 21:56:22 +0200 Subject: [PATCH 081/176] Add game panel for MacOS & rework parts of the user interface --- src/main/java/brainwine/Main.java | 36 ++- .../java/brainwine/ServerStatusListener.java | 9 + src/main/java/brainwine/gui/GameLauncher.java | 233 ++++++++++++++++++ src/main/java/brainwine/gui/GamePanel.java | 160 ------------ src/main/java/brainwine/gui/GameSettings.java | 62 +++++ .../java/brainwine/gui/GuiPreferences.java | 87 ++----- .../java/brainwine/gui/SettingsPanel.java | 231 ----------------- .../brainwine/gui/component/GamePanel.java | 215 ++++++++++++++++ .../gui/{ => component}/ServerPanel.java | 65 +++-- .../gui/component/SettingsPanel.java | 111 +++++++++ .../brainwine/gui/task/FileDownloadTask.java | 79 ++++++ .../brainwine/gui/task/ZipExtractTask.java | 84 +++++++ .../brainwine/gui/theme/ThemeManager.java | 3 +- .../brainwine/gui/{ => view}/MainView.java | 27 +- .../java/brainwine/gui/view/ProgressView.java | 70 ++++++ src/main/java/brainwine/patch/BytePatch.java | 41 +++ .../java/brainwine/patch/DynamicPatch.java | 47 ++++ src/main/java/brainwine/patch/PatchFile.java | 104 ++++++++ .../java/brainwine/util/DesktopUtils.java | 5 +- src/main/java/brainwine/util/SwingUtils.java | 46 +++- .../resources/patches/deepworld-1.13.3.patch | 28 +++ .../resources/patches/deepworld-1.3.10.patch | 10 + .../resources/patches/deepworld-2.11.1.patch | 29 +++ 23 files changed, 1264 insertions(+), 518 deletions(-) create mode 100644 src/main/java/brainwine/ServerStatusListener.java create mode 100644 src/main/java/brainwine/gui/GameLauncher.java delete mode 100644 src/main/java/brainwine/gui/GamePanel.java create mode 100644 src/main/java/brainwine/gui/GameSettings.java delete mode 100644 src/main/java/brainwine/gui/SettingsPanel.java create mode 100644 src/main/java/brainwine/gui/component/GamePanel.java rename src/main/java/brainwine/gui/{ => component}/ServerPanel.java (78%) create mode 100644 src/main/java/brainwine/gui/component/SettingsPanel.java create mode 100644 src/main/java/brainwine/gui/task/FileDownloadTask.java create mode 100644 src/main/java/brainwine/gui/task/ZipExtractTask.java rename src/main/java/brainwine/gui/{ => view}/MainView.java (89%) create mode 100644 src/main/java/brainwine/gui/view/ProgressView.java create mode 100644 src/main/java/brainwine/patch/BytePatch.java create mode 100644 src/main/java/brainwine/patch/DynamicPatch.java create mode 100644 src/main/java/brainwine/patch/PatchFile.java create mode 100644 src/main/resources/patches/deepworld-1.13.3.patch create mode 100644 src/main/resources/patches/deepworld-1.3.10.patch create mode 100644 src/main/resources/patches/deepworld-2.11.1.patch diff --git a/src/main/java/brainwine/Main.java b/src/main/java/brainwine/Main.java index 9f09f3f9..f48ab7ee 100644 --- a/src/main/java/brainwine/Main.java +++ b/src/main/java/brainwine/Main.java @@ -6,6 +6,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; import javax.swing.ImageIcon; import javax.swing.JOptionPane; @@ -17,8 +19,9 @@ import org.apache.logging.log4j.Logger; import brainwine.gui.GuiPreferences; -import brainwine.gui.MainView; import brainwine.gui.theme.ThemeManager; +import brainwine.gui.view.MainView; +import brainwine.util.OperatingSystem; import brainwine.util.SwingUtils; public class Main { @@ -26,8 +29,8 @@ public class Main { private static Logger logger = LogManager.getLogger(); private static boolean disableGui = false; private static boolean forceGui = false; + private final List listeners = new ArrayList<>(); private ServerThread serverThread; - private MainView mainView; private boolean closeRequested; public static void main(String[] args) { @@ -102,12 +105,11 @@ private void createMainView() throws InvocationTargetException, InterruptedExcep UIManager.put("Brainwine.settingsIcon", new ImageIcon(getClass().getResource("/settingsIcon16x.png"))); UIManager.put("Brainwine.communityIcon", new ImageIcon(getClass().getResource("/communityIcon16x.png"))); UIManager.put("Brainwine.powerIcon", new ImageIcon(getClass().getResource("/powerIcon16x.png"))); - UIManager.put("Brainwine.consoleFont", new Font("Consolas", Font.PLAIN, 12)); + UIManager.put("Brainwine.consoleFont", new Font(OperatingSystem.isMacOS() ? "Andale Mono" : "Consolas", Font.PLAIN, 12)); UIManager.put("Spinner.editorAlignment", JTextField.LEFT); UIManager.put("TitlePane.unifiedBackground", false); UIManager.put("Button.foreground", UIManager.get("MenuBar.foreground")); - SwingUtils.setDefaultFontSize(Math.min(28, Math.max(10, GuiPreferences.getInt(GuiPreferences.FONT_SIZE_KEY, 15)))); - SwingUtils.setMenuBarEmbedded(GuiPreferences.getBoolean(GuiPreferences.EMBED_MENU_BAR_KEY, true)); + SwingUtils.setDefaultFontSize(Math.min(28, Math.max(10, GuiPreferences.getFontSize()))); // Check read/write permissions if(!checkReadWritePermissions()) { @@ -117,7 +119,7 @@ private void createMainView() throws InvocationTargetException, InterruptedExcep } // Create view - mainView = new MainView(this); + new MainView(this); }); } @@ -126,8 +128,17 @@ private boolean checkReadWritePermissions() { return Files.isReadable(path) && Files.isWritable(path); } + public void toggleServer() { + if(isServerRunning()) { + stopServer(); + } else { + startServer(); + } + } + public void startServer() { if(!isServerRunning()) { + listeners.forEach(ServerStatusListener::onServerStarting); serverThread = new ServerThread(this); serverThread.start(); } @@ -135,25 +146,28 @@ public void startServer() { public void stopServer() { if(isServerRunning()) { + listeners.forEach(ServerStatusListener::onServerStopping); serverThread.stopGracefully(); } } public void onServerStarted() { - if(mainView != null) { - SwingUtilities.invokeLater(mainView::enableServerButton); - } + listeners.forEach(ServerStatusListener::onServerStarted); } public void onServerStopped() { + listeners.forEach(ServerStatusListener::onServerStopped); + if(closeRequested) { System.exit(0); - } else if(mainView != null) { - SwingUtilities.invokeLater(mainView::enableServerButton); } } public boolean isServerRunning() { return serverThread != null && serverThread.isRunning(); } + + public void addServerStatusListener(ServerStatusListener listener) { + listeners.add(listener); + } } diff --git a/src/main/java/brainwine/ServerStatusListener.java b/src/main/java/brainwine/ServerStatusListener.java new file mode 100644 index 00000000..f03c831e --- /dev/null +++ b/src/main/java/brainwine/ServerStatusListener.java @@ -0,0 +1,9 @@ +package brainwine; + +public interface ServerStatusListener { + + public void onServerStarting(); + public void onServerStopping(); + public void onServerStarted(); + public void onServerStopped(); +} diff --git a/src/main/java/brainwine/gui/GameLauncher.java b/src/main/java/brainwine/gui/GameLauncher.java new file mode 100644 index 00000000..23c623dc --- /dev/null +++ b/src/main/java/brainwine/gui/GameLauncher.java @@ -0,0 +1,233 @@ +package brainwine.gui; + +import static brainwine.gui.GuiConstants.DEEPWORLD_ASSEMBLY_PATH; +import static brainwine.gui.GuiConstants.DEEPWORLD_PLAYERPREFS; +import static brainwine.gui.GuiConstants.HTTP_STEAM_DOWNLOAD_URL; +import static brainwine.gui.GuiConstants.STEAM_REGISTRY_LOCATION; +import static brainwine.gui.GuiConstants.STEAM_RUN_GAME_URL; + +import java.awt.Desktop; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; + +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JOptionPane; +import javax.swing.SwingWorker; + +import brainwine.gui.task.FileDownloadTask; +import brainwine.gui.task.ZipExtractTask; +import brainwine.gui.view.ProgressView; +import brainwine.patch.PatchFile; +import brainwine.util.DesktopUtils; +import brainwine.util.ProcessResult; +import brainwine.util.RegistryKey; +import brainwine.util.RegistryUtils; +import brainwine.util.SwingUtils; + +public abstract class GameLauncher { + + protected final JComponent owner; + + public GameLauncher(JComponent owner) { + this.owner = owner; + } + + public abstract void startGame(); + + /** + * Windows game launcher + */ + public static class Windows extends GameLauncher { + + public Windows(JComponent owner) { + super(owner); + } + + @Override + public void startGame() { + // Show option to download Steam if it is not installed + if(!isSteamInstalled()) { + if(JOptionPane.showConfirmDialog(owner, "You need the Steam desktop application to play Deepworld on Windows.\n" + + "Would you like to go to the download page?", "Attention", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { + DesktopUtils.browseUrl(HTTP_STEAM_DOWNLOAD_URL); + } + + return; + } + + // Update registry keys + String serverAddress = GameSettings.getServerAddress(); + String gatewayPort = String.format(":%s", GameSettings.getGatewayPort()); + String apiPort = String.format(":%s", GameSettings.getApiPort()); + boolean appendPort = !serverAddress.equals("local"); + ProcessResult addGatewayResult = RegistryUtils.add(DEEPWORLD_PLAYERPREFS, "gateway", String.format("%s%s", serverAddress, appendPort ? gatewayPort : "")); + ProcessResult addApiResult = RegistryUtils.add(DEEPWORLD_PLAYERPREFS, "api", String.format("%s%s", serverAddress, appendPort ? apiPort : "")); + + if(!addGatewayResult.wasSuccessful() || !addApiResult.wasSuccessful()) { + JOptionPane.showMessageDialog(owner, "Couldn't update gateway/API host settings in the registry." + + " You may have to do it manually.", "Error", JOptionPane.ERROR_MESSAGE); + } + + // Check if the game is patched + if(!serverAddress.equals("local") && !isGamePatched()) { + JOptionPane.showMessageDialog(owner, + "It appears that the game has not been patched." + + " The world search function will likely not work if this is the case." + + " To patch the game, please follow the instructions on the GitHub repository.", + "Attention", JOptionPane.WARNING_MESSAGE); + } + + // Start the game! + DesktopUtils.browseUrl(STEAM_RUN_GAME_URL); + } + + private boolean isSteamInstalled() { + RegistryKey steamKey = RegistryUtils.getFirstQueryResult(RegistryUtils.query(STEAM_REGISTRY_LOCATION)); + return steamKey != null; + } + + private boolean isGamePatched() { + RegistryKey steamPathKey = RegistryUtils.getFirstQueryResult( + RegistryUtils.query(STEAM_REGISTRY_LOCATION, "SteamPath")); + + if(steamPathKey != null) { + String assemblyPath = steamPathKey.getValue() + DEEPWORLD_ASSEMBLY_PATH; + File file = new File(assemblyPath); + + // Won't always be 100% accurate but it does the job. + if(file.length() == 3916800) { + return false; + } + } + + return true; + } + } + + /** + * MacOS game launcher + */ + public static class Mac extends GameLauncher { + + private final JButton startButton; + private SwingWorker currentTask; + + public Mac(JComponent owner, JButton startButton) { + super(owner); + this.startButton = startButton; + } + + @Override + public void startGame() { + String serverAddress = GameSettings.getServerAddress(); + String version = GameSettings.getGameVersion(); + startGame(serverAddress, version); + } + + private void startGame(String serverAddress, String version) { + File clientDirectory = new File("clients", String.format("v%s", version)); + File applicationFile = new File(clientDirectory, "Deepworld.app"); + File binaryFile = new File(applicationFile, "Contents/MacOS/Deepworld"); + + // Download game if necessary + if(!binaryFile.exists()) { + if(JOptionPane.showConfirmDialog(owner, String.format("Couldn't find game client for Deepworld v%s\nDo you want to download it?", version), + "Attention", JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) { + return; + } + + // The callback will not run if download or extraction fails + downloadGameClient(version, clientDirectory, () -> startGame(serverAddress, version)); + return; + } + + // chmod +x + binaryFile.setExecutable(true); + + // Patch the game binary + try { + if(!patchGameBinary(binaryFile, serverAddress, version)) { + return; + } + } catch(IOException e) { + SwingUtils.showExceptionInfo(owner, "Couldn't patch game binary.", e); + return; + } + + // Launch the application + try { + Desktop.getDesktop().open(applicationFile); + } catch(IOException e) { + SwingUtils.showExceptionInfo(owner, "Couldn't launch application.", e); + } + } + + private boolean patchGameBinary(File binaryFile, String serverAddress, String version) throws IOException { + PatchFile patchFile = null; + + // Load patch file + try(InputStream inputStream = getClass().getResourceAsStream(String.format("/patches/deepworld-%s.patch", version))) { + patchFile = new PatchFile(inputStream); + } + + try(RandomAccessFile file = new RandomAccessFile(binaryFile, "rw")) { + serverAddress = (serverAddress.isEmpty() || serverAddress.equals("local")) ? "http://127.0.0.1:5001" : serverAddress; + byte[] addressBytes = serverAddress.getBytes(); + + // Check server address length + if(addressBytes.length > 33) { + JOptionPane.showMessageDialog(owner, "Server address may not exceed 33 characters in length.", "Attention", JOptionPane.WARNING_MESSAGE); + return false; + } + + // Apply patches + patchFile.apply(file, patch -> { + patch.setBytes("gateway_host", addressBytes); + patch.setInt("gateway_host_strlen", addressBytes.length); + }); + } + + return true; + } + + private void downloadGameClient(String version, File outputDirectory, Runnable callback) { + ProgressView progressView = new ProgressView(owner, "Downloading..."); + progressView.addCloseListener(() -> { + if(currentTask != null && !currentTask.isDone()) { + currentTask.cancel(true); + } + }); + + startButton.setEnabled(false); + String url = String.format("https://github.com/kuroppoi/deepworld-binaries/releases/download/MacOS/deepworld-%s.zip", version); + FileDownloadTask downloadTask = new FileDownloadTask(url, file -> { + if(file == null) { + startButton.setEnabled(true); + progressView.dispose(); + return; + } + + ZipExtractTask extractTask = new ZipExtractTask(file, outputDirectory, success -> { + startButton.setEnabled(true); + progressView.dispose(); + + if(success) { + callback.run(); + } + }); + + progressView.setText("Extracting..."); + currentTask = extractTask; + extractTask.addPropertyChangeListener(event -> progressView.setProgress(extractTask.getProgress())); + extractTask.execute(); + }); + + currentTask = downloadTask; + downloadTask.addPropertyChangeListener(event -> progressView.setProgress(downloadTask.getProgress())); + downloadTask.execute(); + } + } +} diff --git a/src/main/java/brainwine/gui/GamePanel.java b/src/main/java/brainwine/gui/GamePanel.java deleted file mode 100644 index 9c164ea7..00000000 --- a/src/main/java/brainwine/gui/GamePanel.java +++ /dev/null @@ -1,160 +0,0 @@ -package brainwine.gui; - -import static brainwine.gui.GuiConstants.DEEPWORLD_ASSEMBLY_PATH; -import static brainwine.gui.GuiConstants.DEEPWORLD_PLAYERPREFS; -import static brainwine.gui.GuiConstants.HTTP_COMMUNITY_HUB_URL; -import static brainwine.gui.GuiConstants.HTTP_STEAM_DOWNLOAD_URL; -import static brainwine.gui.GuiConstants.STEAM_COMMUNITY_HUB_URL; -import static brainwine.gui.GuiConstants.STEAM_REGISTRY_LOCATION; -import static brainwine.gui.GuiConstants.STEAM_RUN_GAME_URL; -import static brainwine.shared.LogMarkers.GUI_MARKER; - -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.GridBagLayout; -import java.awt.GridLayout; -import java.awt.LinearGradientPaint; -import java.io.File; -import java.io.IOException; - -import javax.imageio.ImageIO; -import javax.swing.JButton; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.UIManager; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import brainwine.gui.component.ImagePanel; -import brainwine.util.DesktopUtils; -import brainwine.util.ProcessResult; -import brainwine.util.RegistryKey; -import brainwine.util.RegistryUtils; -import brainwine.util.SwingUtils; - -@SuppressWarnings("serial") -public class GamePanel extends ImagePanel { - - private static final Logger logger = LogManager.getLogger(); - private final LinearGradientPaint gradientPaint = new LinearGradientPaint(0, 0, 0, 15, - new float[] {0.0F, 1.0F}, new Color[] {Color.BLACK, new Color(0, 0, 0, 0)}); - private final JButton startGameButton; - private final JButton communityHubButton; - - public GamePanel(MainView mainView) { - setLayout(new GridBagLayout()); - - // Host settings button - JButton hostSettingsButton = new JButton("Host Settings", UIManager.getIcon("Brainwine.settingsIcon")); - hostSettingsButton.addActionListener(event -> mainView.showHostSettings()); - - // Community hub button - communityHubButton = new JButton("Community Hub", UIManager.getIcon("Brainwine.communityIcon")); - communityHubButton.addActionListener(event -> openCommunityHub()); - - // Start game button - startGameButton = new JButton("Start Deepworld", UIManager.getIcon("Brainwine.playIcon")); - startGameButton.addActionListener(event -> startGame()); - - // Button panel - JPanel buttonPanel = new JPanel(new GridBagLayout()); - buttonPanel.setOpaque(false); - - JPanel topPanel = new JPanel(new GridLayout(1, 2)); - topPanel.setOpaque(false); - topPanel.add(hostSettingsButton); - topPanel.add(communityHubButton); - buttonPanel.add(topPanel, SwingUtils.createConstraints(0, 0)); - buttonPanel.add(startGameButton, SwingUtils.createConstraints(0, 1, 2, 1)); - add(buttonPanel); - - // Load & set background image - try { - setImage(ImageIO.read(getClass().getResourceAsStream("/background.jpg"))); - } catch (IllegalArgumentException | IOException e) { - logger.error(GUI_MARKER, "Could not load background image", e); - } - } - - @Override - public void paintComponent(Graphics graphics) { - super.paintComponent(graphics); - - // Draw shadow gradient below the title bar if this panel has a background image - if(getImage() != null) { - Graphics2D g2d = (Graphics2D)graphics; - g2d.setPaint(gradientPaint); - g2d.fillRect(0, 0, getWidth(), 15); - } - } - - private void startGame() { - // Show option to download Steam if it is not installed - if(!isSteamInstalled()) { - if(JOptionPane.showConfirmDialog(getRootPane(), "You need the Steam desktop application to play Deepworld on Windows.\n" - + "Would you like to go to the download page?", "Attention", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { - DesktopUtils.browseUrl(HTTP_STEAM_DOWNLOAD_URL); - } - - return; - } - - // Update registry keys - String gatewayHost = GuiPreferences.getString(GuiPreferences.GATEWAY_HOST_KEY, "local"); - String apiHost = GuiPreferences.getString(GuiPreferences.API_HOST_KEY, "local"); - ProcessResult addGatewayResult = RegistryUtils.add(DEEPWORLD_PLAYERPREFS, "gateway", gatewayHost.isEmpty() ? "local" : gatewayHost); - ProcessResult addApiResult = RegistryUtils.add(DEEPWORLD_PLAYERPREFS, "api", apiHost.isEmpty() ? "local" : apiHost); - - if(!addGatewayResult.wasSuccessful() || !addApiResult.wasSuccessful()) { - JOptionPane.showMessageDialog(getRootPane(), "Couldn't update gateway/api host settings in the registry." - + " You may have to do it manually.", "Error", JOptionPane.ERROR_MESSAGE); - } - - // Check if the game is patched - if(!isGamePatched()) { - JOptionPane.showMessageDialog(getRootPane(), - "It appears that the game has not been patched." - + " API features such as the zone searcher will likely not work if this is the case." - + " To patch the game, please follow the instructions on the GitHub repository.", - "Attention", JOptionPane.WARNING_MESSAGE); - } - - // Start the game! - DesktopUtils.browseUrl(STEAM_RUN_GAME_URL); - } - - private void openCommunityHub() { - // If Steam is not installed, open the community hub in a web browser - if(!isSteamInstalled()) { - DesktopUtils.browseUrl(HTTP_COMMUNITY_HUB_URL); - return; - } - - // Otherwise, open it through Steam! - DesktopUtils.browseUrl(STEAM_COMMUNITY_HUB_URL); - } - - private boolean isSteamInstalled() { - RegistryKey steamKey = RegistryUtils.getFirstQueryResult(RegistryUtils.query(STEAM_REGISTRY_LOCATION)); - return steamKey != null; - } - - private boolean isGamePatched() { - RegistryKey steamPathKey = RegistryUtils.getFirstQueryResult( - RegistryUtils.query(STEAM_REGISTRY_LOCATION, "SteamPath")); - - if(steamPathKey != null) { - String assemblyPath = steamPathKey.getValue() + DEEPWORLD_ASSEMBLY_PATH; - File file = new File(assemblyPath); - - // Won't always be 100% accurate but it does the job. - if(file.length() == 3916800) { - return false; - } - } - - return true; - } -} diff --git a/src/main/java/brainwine/gui/GameSettings.java b/src/main/java/brainwine/gui/GameSettings.java new file mode 100644 index 00000000..74856fbc --- /dev/null +++ b/src/main/java/brainwine/gui/GameSettings.java @@ -0,0 +1,62 @@ +package brainwine.gui; + +import java.util.prefs.Preferences; + +import brainwine.gui.component.GamePanel; +import brainwine.util.OperatingSystem; + +public class GameSettings { + + // Keys + public static final String GAME_VERSION_KEY = "gameVersion"; + public static final String SERVER_ADDRESS_KEY = "serverAddress"; + public static final String GATEWAY_PORT_KEY = "gatewayPort"; + public static final String API_PORT_KEY = "apiPort"; + + // Defaults + public static final String GAME_VERSION_DEFAULT = GamePanel.MAC_GAME_VERSIONS[0]; + public static final String SERVER_ADDRESS_DEFAULT = OperatingSystem.isWindows() ? "local" : "http://127.0.0.1:5001"; + public static final int GATEWAY_PORT_DEFAULT = 5001; + public static final int API_PORT_DEFAULT = 5003; + + private static Preferences preferences = Preferences.userRoot().node(GameSettings.class.getName()); + + public static void resetToDefaults() { + setGameVersion(GAME_VERSION_DEFAULT); + setServerAddress(SERVER_ADDRESS_DEFAULT); + setGatewayPort(GATEWAY_PORT_DEFAULT); + setApiPort(API_PORT_DEFAULT); + } + + public static void setGameVersion(String value) { + preferences.put(GAME_VERSION_KEY, value); + } + + public static String getGameVersion() { + return preferences.get(GAME_VERSION_KEY, GAME_VERSION_DEFAULT); + } + + public static void setServerAddress(String value) { + preferences.put(SERVER_ADDRESS_KEY, value); + } + + public static String getServerAddress() { + return preferences.get(SERVER_ADDRESS_KEY, SERVER_ADDRESS_DEFAULT); + } + + public static void setGatewayPort(int value) { + preferences.putInt(GATEWAY_PORT_KEY, value); + } + + public static int getGatewayPort() { + return preferences.getInt(GATEWAY_PORT_KEY, GATEWAY_PORT_DEFAULT); + } + + public static void setApiPort(int value) { + preferences.putInt(API_PORT_KEY, value); + } + + public static int getApiPort() { + return preferences.getInt(API_PORT_KEY, API_PORT_DEFAULT); + } +} diff --git a/src/main/java/brainwine/gui/GuiPreferences.java b/src/main/java/brainwine/gui/GuiPreferences.java index 92d416cb..077ec735 100644 --- a/src/main/java/brainwine/gui/GuiPreferences.java +++ b/src/main/java/brainwine/gui/GuiPreferences.java @@ -1,87 +1,50 @@ package brainwine.gui; -import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; +import com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialDarkerIJTheme; + public class GuiPreferences { - - public static final String ROOT_KEY = "brainwine"; + + // Keys public static final String THEME_KEY = "theme"; public static final String TAB_PLACEMENT_KEY = "tabPlacement"; public static final String FONT_SIZE_KEY = "fontSize"; - public static final String EMBED_MENU_BAR_KEY = "embedMenuBar"; - public static final String GATEWAY_HOST_KEY = "gatewayHost"; - public static final String API_HOST_KEY = "apiHost"; - private static Preferences preferences; - - public static Preferences get() { - if(preferences == null) { - preferences = Preferences.userRoot().node(ROOT_KEY); - } - - return preferences; - } - - public static void setString(String key, String value) { - get().put(key, value); - } - - public static String getString(String key, String def) { - return get().get(key, def); - } - public static void setBoolean(String key, boolean value) { - get().putBoolean(key, value); - } - - public static boolean getBoolean(String key, boolean def) { - return get().getBoolean(key, def); - } - - public static void setInt(String key, int value) { - get().putInt(key, value); - } + // Defaults + public static final String THEME_DEFAULT = FlatMaterialDarkerIJTheme.class.getName(); + public static final int TAB_PLACEMENT_DEFAULT = 1; + public static final int FONT_SIZE_DEFAULT = 16; - public static int getInt(String key, int def) { - return get().getInt(key, def); - } - - public static void setFloat(String key, float value) { - get().putFloat(key, value); - } - - public static float getFloat(String key, float def) { - return get().getFloat(key, def); - } + private static Preferences preferences = Preferences.userRoot().node(GuiPreferences.class.getName()); - public static void setLong(String key, long value) { - get().putLong(key, value); + public static void resetToDefaults() { + setTheme(THEME_DEFAULT); + setTabPlacement(TAB_PLACEMENT_DEFAULT); + setFontSize(FONT_SIZE_DEFAULT); } - public static long getLong(String key, long def) { - return get().getLong(key, def); + public static void setTheme(String value) { + preferences.put(THEME_KEY, value); } - public static void setDouble(String key, double value) { - get().putDouble(key, value); + public static String getTheme() { + return preferences.get(THEME_KEY, THEME_DEFAULT); } - public static double getDouble(String key, double def) { - return get().getDouble(key, def); + public static void setTabPlacement(int value) { + preferences.putInt(TAB_PLACEMENT_KEY, value); } - public static void setByteArray(String key, byte[] value) { - get().putByteArray(key, value); + public static int getTabPlacement() { + return preferences.getInt(TAB_PLACEMENT_KEY, TAB_PLACEMENT_DEFAULT); } - public static byte[] getByteArray(String key, byte[] def) { - return get().getByteArray(key, def); + public static void setFontSize(int value) { + preferences.putInt(FONT_SIZE_KEY, value); } - public static void clear() throws BackingStoreException { - if(preferences != null) { - preferences.removeNode(); - preferences = null; - } + public static int getFontSize() { + return preferences.getInt(FONT_SIZE_KEY, FONT_SIZE_DEFAULT); } } diff --git a/src/main/java/brainwine/gui/SettingsPanel.java b/src/main/java/brainwine/gui/SettingsPanel.java deleted file mode 100644 index ca124e59..00000000 --- a/src/main/java/brainwine/gui/SettingsPanel.java +++ /dev/null @@ -1,231 +0,0 @@ -package brainwine.gui; - -import java.awt.Color; -import java.awt.Dimension; -import java.awt.GridBagLayout; -import java.awt.GridLayout; -import java.util.prefs.BackingStoreException; - -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JSpinner; -import javax.swing.SpinnerNumberModel; -import javax.swing.SwingUtilities; -import javax.swing.border.Border; -import javax.swing.border.TitledBorder; -import javax.swing.event.DocumentEvent; - -import com.formdev.flatlaf.extras.components.FlatScrollPane; -import com.formdev.flatlaf.extras.components.FlatTextField; -import com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialDarkerIJTheme; - -import brainwine.gui.event.DocumentChangeListener; -import brainwine.gui.theme.Theme; -import brainwine.gui.theme.ThemeManager; -import brainwine.util.OperatingSystem; -import brainwine.util.SwingUtils; - -@SuppressWarnings("serial") -public class SettingsPanel extends JPanel { - - private final MainView mainView; - private JComboBox themeBox; - private JComboBox tabPlacementBox; - private JSpinner fontSizeSpinner; - private JCheckBox embedMenuBarCheckbox; - private FlatTextField gatewayHostField; - private FlatTextField apiHostField; - - public SettingsPanel(MainView mainView) { - this.mainView = mainView; - - // Reset Button - JButton resetButton = new JButton("Reset to Defaults"); - resetButton.addActionListener(event -> resetSettings(true)); - - // Clear Button - JButton clearButton = new JButton("Clear Settings"); - clearButton.addActionListener(event -> clearSettings()); - - // Button Panel - JPanel buttonPanel = new JPanel(new GridLayout(1, 2)); - buttonPanel.setBorder(createCategoryBorder("Reset Settings")); - buttonPanel.add(resetButton); - buttonPanel.add(clearButton); - - // Main panel - JPanel settingsPanel = new JPanel(new GridBagLayout()); - settingsPanel.add(createVisualSettingsPanel(), SwingUtils.createConstraints(0, 0)); - - if(OperatingSystem.isWindows()) { - settingsPanel.add(createGameSettingsPanel(), SwingUtils.createConstraints(0, 1)); - } - - settingsPanel.add(buttonPanel, SwingUtils.createConstraints(0, 2)); - - // Scroll pane (TODO doesn't actually scroll) - FlatScrollPane scrollPane = new FlatScrollPane(); - scrollPane.setBorder(BorderFactory.createEmptyBorder()); - scrollPane.setViewportView(settingsPanel); - scrollPane.setShowButtons(true); - scrollPane.setFocusable(false); - add(scrollPane); - } - - private JPanel createVisualSettingsPanel() { - // Theme selector box - themeBox = new JComboBox<>(); - ThemeManager.getThemes().forEach(themeBox::addItem); - themeBox.setSelectedItem(ThemeManager.getCurrentTheme()); - themeBox.addActionListener(event -> setThemePreference((Theme)themeBox.getSelectedItem())); - - // Tabbed pane orientation box - tabPlacementBox = new JComboBox<>(new String[] {"Top", "Left", "Bottom", "Right"}); - tabPlacementBox.setSelectedIndex(mainView.getTabPlacement() - 1); - tabPlacementBox.addActionListener(event -> setTabPlacementPreference(tabPlacementBox.getSelectedIndex() + 1)); - - // Font size changer - fontSizeSpinner = new JSpinner(new SpinnerNumberModel(SwingUtils.getDefaultFontSize(), 10, 28, 1)); - fontSizeSpinner.addChangeListener(event -> setFontSizePreference((int)fontSizeSpinner.getValue())); - - // Menu bar embed checkbox - embedMenuBarCheckbox = new JCheckBox(); - embedMenuBarCheckbox.setSelected(SwingUtils.isMenuBarEmbedded()); - embedMenuBarCheckbox.addChangeListener(event -> setEmbedMenuBarPreference(embedMenuBarCheckbox.isSelected())); - - // Panel - JPanel panel = new JPanel(new GridBagLayout()); - panel.setBorder(createCategoryBorder("Visual Settings")); - panel.add(new JLabel("Theme"), SwingUtils.createConstraints(0, 0)); - panel.add(themeBox, SwingUtils.createConstraints(1, 0)); - panel.add(new JLabel("Tab Placement"), SwingUtils.createConstraints(0, 1)); - panel.add(tabPlacementBox, SwingUtils.createConstraints(1, 1)); - panel.add(new JLabel("Font Size"), SwingUtils.createConstraints(0, 2)); - panel.add(fontSizeSpinner, SwingUtils.createConstraints(1, 2)); - panel.add(new JLabel("Embed Menu Bar"), SwingUtils.createConstraints(0, 3)); - panel.add(embedMenuBarCheckbox, SwingUtils.createConstraints(1, 3)); - return panel; - } - - private JPanel createGameSettingsPanel() { - // Gateway host field - gatewayHostField = new FlatTextField() { - @Override - public Dimension getPreferredSize() { - return themeBox.getPreferredSize(); - } - }; - gatewayHostField.setText(GuiPreferences.getString(GuiPreferences.GATEWAY_HOST_KEY, "local")); - gatewayHostField.setPlaceholderText("127.0.0.1:5001"); - gatewayHostField.getDocument().addDocumentListener(new DocumentChangeListener() { - @Override - public void changedUpdate(DocumentEvent event) { - GuiPreferences.setString(GuiPreferences.GATEWAY_HOST_KEY, gatewayHostField.getText()); - } - }); - - // API host field - apiHostField = new FlatTextField() { - @Override - public Dimension getPreferredSize() { - return themeBox.getPreferredSize(); - } - }; - apiHostField.setText(GuiPreferences.getString(GuiPreferences.API_HOST_KEY, "local")); - apiHostField.setPlaceholderText("127.0.0.1:5003"); - apiHostField.getDocument().addDocumentListener(new DocumentChangeListener() { - @Override - public void changedUpdate(DocumentEvent event) { - GuiPreferences.setString(GuiPreferences.API_HOST_KEY, apiHostField.getText()); - } - }); - - // Panel - JPanel panel = new JPanel(new GridBagLayout()); - panel.setBorder(createCategoryBorder("Game Settings")); - panel.add(new JLabel("Gateway Host"), SwingUtils.createConstraints(0, 0)); - panel.add(gatewayHostField, SwingUtils.createConstraints(1, 0, 1, 1, 0, 0)); - panel.add(new JLabel("API Host"), SwingUtils.createConstraints(0, 1)); - panel.add(apiHostField, SwingUtils.createConstraints(1, 1, 1, 1, 0, 0)); - return panel; - } - - public void focusHostSettings() { - gatewayHostField.requestFocus(); - } - - private void setThemePreference(Theme theme) { - SwingUtilities.invokeLater(() -> { - ThemeManager.setTheme(theme); - }); - - GuiPreferences.setString(GuiPreferences.THEME_KEY, theme.getClassName()); - } - - private void setTabPlacementPreference(int tabPlacement) { - SwingUtilities.invokeLater(() -> { - mainView.setTabPlacement(tabPlacement); - }); - - GuiPreferences.setInt(GuiPreferences.TAB_PLACEMENT_KEY, tabPlacement); - } - - private void setFontSizePreference(int fontSize) { - SwingUtils.setDefaultFontSize(fontSize); - GuiPreferences.setInt(GuiPreferences.FONT_SIZE_KEY, fontSize); - } - - private void setEmbedMenuBarPreference(boolean embedMenuBar) { - SwingUtils.setMenuBarEmbedded(embedMenuBar); - GuiPreferences.setBoolean(GuiPreferences.EMBED_MENU_BAR_KEY, embedMenuBar); - } - - private void resetSettings(boolean showPrompt) { - if(!showPrompt || JOptionPane.showConfirmDialog(getRootPane(), - "Are you sure you want to reset all settings to their default values?", - "Confirmation", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { - themeBox.setSelectedItem(ThemeManager.getTheme(FlatMaterialDarkerIJTheme.class)); - tabPlacementBox.setSelectedIndex(0); - fontSizeSpinner.setValue(15); - embedMenuBarCheckbox.setSelected(true); - - if(OperatingSystem.isWindows()) { - gatewayHostField.setText("local"); - apiHostField.setText("local"); - } - - if(showPrompt) { - JOptionPane.showMessageDialog(getRootPane(), "Settings reset successfully."); - } - } - } - - private void clearSettings() { - if(JOptionPane.showConfirmDialog(getRootPane(), - "This will reset all settings to their default values and remove them from this user's preferences" - + " until you change them again or restart the application. Are you sure you want to continue?", - "Confirmation", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { - resetSettings(false); - - // Invoke later because settings are not reset immediately. - SwingUtilities.invokeLater(() -> { - try { - GuiPreferences.clear(); - JOptionPane.showMessageDialog(getRootPane(), "Settings cleared successfully."); - } catch(BackingStoreException e) { - JOptionPane.showMessageDialog(getRootPane(), "Could not clear preferences: " + e.getMessage(), - "Error", JOptionPane.ERROR_MESSAGE); - } - }); - } - } - - private Border createCategoryBorder(String category) { - return BorderFactory.createTitledBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.LIGHT_GRAY), category, TitledBorder.CENTER, TitledBorder.CENTER); - } -} diff --git a/src/main/java/brainwine/gui/component/GamePanel.java b/src/main/java/brainwine/gui/component/GamePanel.java new file mode 100644 index 00000000..844bd905 --- /dev/null +++ b/src/main/java/brainwine/gui/component/GamePanel.java @@ -0,0 +1,215 @@ +package brainwine.gui.component; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.GradientPaint; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.io.IOException; + +import javax.imageio.ImageIO; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; + +import com.formdev.flatlaf.extras.components.FlatTextField; + +import brainwine.Main; +import brainwine.ServerStatusListener; +import brainwine.gui.GameLauncher; +import brainwine.gui.GameSettings; +import brainwine.gui.GuiConstants; +import brainwine.util.DesktopUtils; +import brainwine.util.OperatingSystem; +import brainwine.util.SwingUtils; + +@SuppressWarnings("serial") +public class GamePanel extends JPanel { + + public static final String[] MAC_GAME_VERSIONS = { "2.11.1", "1.13.3" }; + private final Main main; + private final GameLauncher gameLauncher; + private JButton serverButton; + private JButton startGameButton; + private FlatTextField serverAddressField; + private FlatTextField gatewayPortField; // Windows only + private FlatTextField apiPortField; // Windows only + private JComboBox gameVersionBox; // Mac only + + public GamePanel(Main main) { + this.main = main; + setLayout(new BorderLayout()); + + // Create image panel + ImagePanel imagePanel = new ImagePanel() { + @Override + public void paintComponent(Graphics graphics) { + super.paintComponent(graphics); + Graphics2D g2d = (Graphics2D)graphics; + Color gradientColor = new Color(8, 8, 8, 255); + Color fadeColor = new Color(8, 8, 8, 0); + g2d.setPaint(new GradientPaint(0, 0, gradientColor, 0, 20, fadeColor)); + g2d.fillRect(0, 0, getWidth(), 20); + g2d.setPaint(new GradientPaint(0, getHeight() - 20, fadeColor, 0, getHeight(), gradientColor)); + g2d.fillRect(0, getHeight() - 20, getWidth(), 20); + } + }; + + // Load background image + try { + imagePanel.setImage(ImageIO.read(getClass().getResourceAsStream("/background.jpg"))); + } catch (IllegalArgumentException | IOException e) { + SwingUtils.showExceptionInfo(this, "Could not load background image.", e); + } + + // Add components + add(imagePanel); + add(createLauncherPanel(), BorderLayout.SOUTH); // Game launcher relies on this so do not move! + + // Create game launcher + gameLauncher = OperatingSystem.isWindows() ? new GameLauncher.Windows(this) : new GameLauncher.Mac(this, startGameButton); + + // Create server status listener + main.addServerStatusListener(new ServerStatusListener() { + @Override + public void onServerStarting() { + serverButton.setEnabled(false); + } + + @Override + public void onServerStopping() { + serverButton.setEnabled(false); + } + + @Override + public void onServerStarted() { + SwingUtilities.invokeLater(() -> { + serverButton.setText("Stop Server"); + serverButton.setEnabled(true); + }); + } + + @Override + public void onServerStopped() { + SwingUtilities.invokeLater(() -> { + serverButton.setText("Start Server"); + serverButton.setEnabled(true); + }); + } + }); + } + + private void openCommunityHub() { + // Try opening with Steam and open with browser if it fails + if(!DesktopUtils.browseUrl(GuiConstants.STEAM_COMMUNITY_HUB_URL)) { + DesktopUtils.browseUrl(GuiConstants.HTTP_COMMUNITY_HUB_URL); + } + } + + private JPanel createLauncherPanel() { + // Start button + startGameButton = new JButton("Start Deepworld", UIManager.getIcon("Brainwine.playIcon")); + startGameButton.addActionListener(event -> { + // Update preferences + GameSettings.setServerAddress(SwingUtils.getTextFieldValue(serverAddressField)); + + if(OperatingSystem.isWindows()) { + try { + GameSettings.setGatewayPort(Integer.parseInt(SwingUtils.getTextFieldValue(gatewayPortField))); + GameSettings.setApiPort(Integer.parseInt(SwingUtils.getTextFieldValue(apiPortField))); + } catch(NumberFormatException e) { + // Show warning & discard exception silently + JOptionPane.showMessageDialog(this, "Server ports must be numerical.", "Attention", JOptionPane.WARNING_MESSAGE); + return; + } + } else { + GameSettings.setGameVersion((String)gameVersionBox.getSelectedItem()); + } + + // Launch the game + gameLauncher.startGame(); + }); + + // Server button + serverButton = new JButton("Start Server", UIManager.getIcon("Brainwine.serverIcon")); + serverButton.addActionListener(event -> main.toggleServer()); + + // Community hub button + JButton communityHubButton = new JButton("Community Hub", UIManager.getIcon("Brainwine.communityIcon")); + communityHubButton.addActionListener(event -> openCommunityHub()); + + // Create top button panel + JPanel topButtonPanel = new JPanel(new GridLayout(1, 2)); + topButtonPanel.add(serverButton); + topButtonPanel.add(communityHubButton); + + // Create button panel + JPanel buttonPanel = new JPanel(new GridLayout(2, 1)); + buttonPanel.add(topButtonPanel); + buttonPanel.add(startGameButton); + + // Create panel + JPanel panel = new JPanel(new GridLayout(1, 2)); + panel.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 5)); + panel.add(OperatingSystem.isWindows() ? createWindowsGameSettingsPanel() : createMacGameSettingsPanel()); + panel.add(buttonPanel); + return panel; + } + + private JPanel createWindowsGameSettingsPanel() { + // Create server address field + serverAddressField = new FlatTextField(); + serverAddressField.setText(GameSettings.getServerAddress()); + serverAddressField.setPlaceholderText("local"); + serverAddressField.setShowClearButton(true); + + // Create gateway port field + gatewayPortField = new FlatTextField(); + gatewayPortField.setText(String.valueOf(GameSettings.getGatewayPort())); + gatewayPortField.setPlaceholderText("5001"); + gatewayPortField.setShowClearButton(true); + + // Create API port field + apiPortField = new FlatTextField(); + apiPortField.setText(String.valueOf(GameSettings.getApiPort())); + apiPortField.setPlaceholderText("5003"); + apiPortField.setShowClearButton(true); + + // Create panel + JPanel panel = new JPanel(new GridBagLayout()); + panel.add(new JLabel("Server Address"), SwingUtils.createConstraints(0, 0, 1, 1, 0, 1)); + panel.add(serverAddressField, SwingUtils.createConstraints(1, 0, 3, 1)); + panel.add(new JLabel("Gateway Port"), SwingUtils.createConstraints(0, 1)); + panel.add(gatewayPortField, SwingUtils.createConstraints(1, 1, 1, 1, 1, 0)); + panel.add(new JLabel("API Port", JLabel.CENTER), SwingUtils.createConstraints(2, 1)); + panel.add(apiPortField, SwingUtils.createConstraints(3, 1, 1, 1, 1, 0)); + return panel; + } + + private JPanel createMacGameSettingsPanel() { + // Create server address field + serverAddressField = new FlatTextField(); + serverAddressField.setText(GameSettings.getServerAddress()); + serverAddressField.setPlaceholderText("http://127.0.0.1:5001"); + serverAddressField.setShowClearButton(true); + + // Create game version selector + gameVersionBox = new JComboBox<>(MAC_GAME_VERSIONS); + gameVersionBox.setSelectedItem(GameSettings.getGameVersion()); + + // Create panel + JPanel panel = new JPanel(new GridBagLayout()); + panel.add(new JLabel("Server Address"), SwingUtils.createConstraints(0, 0, 1, 1, 0, 1)); + panel.add(serverAddressField, SwingUtils.createConstraints(1, 0, 1, 1)); + panel.add(new JLabel("Game Version"), SwingUtils.createConstraints(0, 1, 1, 1, 0, 1)); + panel.add(gameVersionBox, SwingUtils.createConstraints(1, 1, 1, 1)); + return panel; + } +} diff --git a/src/main/java/brainwine/gui/ServerPanel.java b/src/main/java/brainwine/gui/component/ServerPanel.java similarity index 78% rename from src/main/java/brainwine/gui/ServerPanel.java rename to src/main/java/brainwine/gui/component/ServerPanel.java index efc1e7de..2416ff5a 100644 --- a/src/main/java/brainwine/gui/ServerPanel.java +++ b/src/main/java/brainwine/gui/component/ServerPanel.java @@ -1,4 +1,4 @@ -package brainwine.gui; +package brainwine.gui.component; import static brainwine.gui.GuiConstants.ERROR_COLOR; import static brainwine.gui.GuiConstants.INFO_COLOR; @@ -14,6 +14,7 @@ import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextPane; +import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; @@ -27,8 +28,9 @@ import com.formdev.flatlaf.extras.components.FlatScrollPane; import com.formdev.flatlaf.extras.components.FlatTextField; -import brainwine.Main; import brainwine.ListenableAppender; +import brainwine.Main; +import brainwine.ServerStatusListener; import brainwine.gui.event.AutoScrollAdjustmentListener; @SuppressWarnings("serial") @@ -93,8 +95,41 @@ public Font getFont() { // Server Toggle Button serverButton = new JButton("Start Server", UIManager.getIcon("Brainwine.powerIcon")); - serverButton.addActionListener(event -> toggleServer()); + serverButton.addActionListener(event -> main.toggleServer()); bottomPanel.add(serverButton, BorderLayout.LINE_END); + + // Create server status listener + main.addServerStatusListener(new ServerStatusListener() { + @Override + public void onServerStarting() { + serverButton.setEnabled(false); + consoleOutput.setText(null); + } + + @Override + public void onServerStopping() { + serverButton.setEnabled(false); + consoleInput.setEditable(false); + consoleInput.setText(null); + } + + @Override + public void onServerStarted() { + SwingUtilities.invokeLater(() -> { + serverButton.setText("Stop Server"); + serverButton.setEnabled(true); + consoleInput.setEditable(true); + }); + } + + @Override + public void onServerStopped() { + SwingUtilities.invokeLater(() -> { + serverButton.setText("Start Server"); + serverButton.setEnabled(true); + }); + } + }); } private void appendConsoleOutput(String text, Color color) { @@ -118,28 +153,4 @@ private void processConsoleInput() { consoleInput.setText(null); } } - - private void toggleServer() { - if(main.isServerRunning()) { - serverButton.setEnabled(false); - consoleInput.setEditable(false); - consoleInput.setText(null); - main.stopServer(); - } else { - serverButton.setEnabled(false); - consoleInput.setEditable(true); - consoleOutput.setText(null); - main.startServer(); - } - } - - public void enableServerButton() { - if(!main.isServerRunning()) { - consoleInput.setEditable(false); - consoleInput.setText(null); - } - - serverButton.setText(main.isServerRunning() ? "Stop Server" : "Start Server"); - serverButton.setEnabled(true); - } } diff --git a/src/main/java/brainwine/gui/component/SettingsPanel.java b/src/main/java/brainwine/gui/component/SettingsPanel.java new file mode 100644 index 00000000..8cd0d9d6 --- /dev/null +++ b/src/main/java/brainwine/gui/component/SettingsPanel.java @@ -0,0 +1,111 @@ +package brainwine.gui.component; + +import java.awt.GridBagLayout; +import java.awt.GridLayout; + +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; + +import com.formdev.flatlaf.extras.components.FlatScrollPane; + +import brainwine.gui.GuiPreferences; +import brainwine.gui.theme.Theme; +import brainwine.gui.theme.ThemeManager; +import brainwine.gui.view.MainView; +import brainwine.util.SwingUtils; + +@SuppressWarnings("serial") +public class SettingsPanel extends JPanel { + + private final MainView mainView; + private JComboBox themeBox; + private JComboBox tabPlacementBox; + private JSpinner fontSizeSpinner; + + public SettingsPanel(MainView mainView) { + this.mainView = mainView; + + // Save button + JButton saveButton = new JButton("Save Settings"); + saveButton.addActionListener(event -> saveSettings()); + + // Reset Button + JButton resetButton = new JButton("Reset to Defaults"); + resetButton.addActionListener(event -> resetSettings(true)); + + // Button Panel + JPanel buttonPanel = new JPanel(new GridLayout(1, 2)); + buttonPanel.add(saveButton); + buttonPanel.add(resetButton); + + // Main panel + JPanel settingsPanel = new JPanel(new GridBagLayout()); + settingsPanel.add(createVisualSettingsPanel(), SwingUtils.createConstraints(0, 0)); + settingsPanel.add(buttonPanel, SwingUtils.createConstraints(0, 1)); + + // Scroll pane (TODO doesn't actually scroll) + FlatScrollPane scrollPane = new FlatScrollPane(); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + scrollPane.setViewportView(settingsPanel); + scrollPane.setShowButtons(true); + scrollPane.setFocusable(false); + add(scrollPane); + } + + private JPanel createVisualSettingsPanel() { + // Theme selector box + themeBox = new JComboBox<>(); + ThemeManager.getThemes().forEach(themeBox::addItem); + themeBox.setSelectedItem(ThemeManager.getCurrentTheme()); + themeBox.addActionListener(event -> ThemeManager.setTheme((Theme)themeBox.getSelectedItem())); + + // Tabbed pane orientation box + tabPlacementBox = new JComboBox<>(new String[] {"Top", "Left", "Bottom", "Right"}); + tabPlacementBox.setSelectedIndex(mainView.getTabPlacement() - 1); + tabPlacementBox.addActionListener(event -> mainView.setTabPlacement(tabPlacementBox.getSelectedIndex() + 1)); + + // Font size changer + fontSizeSpinner = new JSpinner(new SpinnerNumberModel(SwingUtils.getDefaultFontSize(), 10, 28, 1)); + fontSizeSpinner.addChangeListener(event -> SwingUtils.setDefaultFontSize((int)fontSizeSpinner.getValue())); + + // Panel + JPanel panel = new JPanel(new GridBagLayout()); + panel.add(new JLabel("Theme"), SwingUtils.createConstraints(0, 0)); + panel.add(themeBox, SwingUtils.createConstraints(1, 0)); + panel.add(new JLabel("Tab Placement"), SwingUtils.createConstraints(0, 1)); + panel.add(tabPlacementBox, SwingUtils.createConstraints(1, 1)); + panel.add(new JLabel("Font Size"), SwingUtils.createConstraints(0, 2)); + panel.add(fontSizeSpinner, SwingUtils.createConstraints(1, 2)); + return panel; + } + + private void saveSettings() { + GuiPreferences.setTheme(((Theme)themeBox.getSelectedItem()).getClassName()); + GuiPreferences.setTabPlacement(tabPlacementBox.getSelectedIndex() + 1); + GuiPreferences.setFontSize((int)fontSizeSpinner.getValue()); + JOptionPane.showMessageDialog(getRootPane(), "Settings have been saved."); + } + + private void resetSettings(boolean showPrompt) { + if(!showPrompt || JOptionPane.showConfirmDialog(getRootPane(), + "Are you sure you want to reset all settings to their default values?", + "Confirmation", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { + GuiPreferences.resetToDefaults(); + + // Update components + themeBox.setSelectedItem(ThemeManager.getTheme(GuiPreferences.getTheme())); + tabPlacementBox.setSelectedIndex(GuiPreferences.getTabPlacement() - 1); + fontSizeSpinner.setValue(GuiPreferences.getFontSize()); + + if(showPrompt) { + JOptionPane.showMessageDialog(getRootPane(), "Settings reset successfully."); + } + } + } +} diff --git a/src/main/java/brainwine/gui/task/FileDownloadTask.java b/src/main/java/brainwine/gui/task/FileDownloadTask.java new file mode 100644 index 00000000..6a62c14b --- /dev/null +++ b/src/main/java/brainwine/gui/task/FileDownloadTask.java @@ -0,0 +1,79 @@ +package brainwine.gui.task; + +import static brainwine.shared.LogMarkers.GUI_MARKER; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; + +import javax.swing.SwingWorker; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import brainwine.util.SwingUtils; + +public class FileDownloadTask extends SwingWorker { + + private static final Logger logger = LogManager.getLogger(); + private final String urlString; + private final Consumer callback; + + public FileDownloadTask(String urlString, Consumer callback) { + this.urlString = urlString; + this.callback = callback; + } + + @Override + protected File doInBackground() throws Exception { + URL url = new URL(urlString); + URLConnection connection = url.openConnection(); + long totalLength = connection.getContentLengthLong(); + int length = 0; + long downloaded = 0; + byte[] buffer = new byte[1024]; + File file = File.createTempFile("download", ".tmp"); + file.deleteOnExit(); + BufferedOutputStream outputStream = null; + + try(BufferedInputStream inputStream = new BufferedInputStream(connection.getInputStream())) { + outputStream = new BufferedOutputStream(new FileOutputStream(file)); + + while((length = inputStream.read(buffer)) != -1) { + downloaded += length; + int progress = (int)(downloaded / (double)totalLength * 100); + setProgress(progress); + outputStream.write(buffer, 0, length); + } + } finally { + if(outputStream != null) { + outputStream.close(); + } + } + + return file; + } + + @Override + protected void done() { + File file = null; + + try { + file = get(); + } catch(ExecutionException e) { + String message = "Could not download file."; + logger.error(GUI_MARKER, message, e.getCause()); + SwingUtils.showExceptionInfo(null, message, e.getCause()); + } catch(CancellationException | InterruptedException e) { + // Discard silently + } + + callback.accept(file); + } +} diff --git a/src/main/java/brainwine/gui/task/ZipExtractTask.java b/src/main/java/brainwine/gui/task/ZipExtractTask.java new file mode 100644 index 00000000..9d79a21c --- /dev/null +++ b/src/main/java/brainwine/gui/task/ZipExtractTask.java @@ -0,0 +1,84 @@ +package brainwine.gui.task; + +import static brainwine.shared.LogMarkers.GUI_MARKER; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import javax.swing.SwingWorker; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import brainwine.util.SwingUtils; + +public class ZipExtractTask extends SwingWorker { + + private static final Logger logger = LogManager.getLogger(); + private final File zipFile; + private final File outputDirectory; + private final Consumer callback; + + public ZipExtractTask(File zipFile, File outputDirectory, Consumer callback) { + this.zipFile = zipFile; + this.outputDirectory = outputDirectory; + this.callback = callback; + } + + @Override + protected Void doInBackground() throws Exception { + byte[] buffer = new byte[1024]; + long totalLength = zipFile.length(); + long extracted = 0; + + try(ZipInputStream inputStream = new ZipInputStream(new FileInputStream(zipFile))) { + ZipEntry entry = null; + + while((entry = inputStream.getNextEntry()) != null) { + if(entry.isDirectory()) { + continue; + } + + File file = new File(outputDirectory, entry.getName()); + file.getParentFile().mkdirs(); + + try(FileOutputStream outputStream = new FileOutputStream(file)) { + int length = 0; + + while((length = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, length); + } + } + + extracted += entry.getCompressedSize(); + inputStream.closeEntry(); + setProgress((int)(extracted / (double)totalLength * 100)); + } + } + + return null; + } + + @Override + protected void done() { + try { + get(); + callback.accept(true); + return; + } catch(ExecutionException e) { + String message = "Could not extract archive."; + logger.error(GUI_MARKER, message, e.getCause()); + SwingUtils.showExceptionInfo(null, message, e.getCause()); + } catch(CancellationException | InterruptedException e) { + // Discard silently + } + + callback.accept(false); + } +} diff --git a/src/main/java/brainwine/gui/theme/ThemeManager.java b/src/main/java/brainwine/gui/theme/ThemeManager.java index ab914513..7a49307c 100644 --- a/src/main/java/brainwine/gui/theme/ThemeManager.java +++ b/src/main/java/brainwine/gui/theme/ThemeManager.java @@ -19,7 +19,6 @@ import com.formdev.flatlaf.extras.FlatAnimatedLafChange; import com.formdev.flatlaf.intellijthemes.FlatAllIJThemes; import com.formdev.flatlaf.intellijthemes.FlatAllIJThemes.FlatIJLookAndFeelInfo; -import com.formdev.flatlaf.intellijthemes.materialthemeuilite.FlatMaterialDarkerIJTheme; import brainwine.gui.GuiPreferences; @@ -43,7 +42,7 @@ public static void init() { } // Set saved or default theme - setTheme(GuiPreferences.getString(GuiPreferences.THEME_KEY, FlatMaterialDarkerIJTheme.class.getName()), false); + setTheme(GuiPreferences.getTheme(), false); } public static void registerTheme(Theme theme) { diff --git a/src/main/java/brainwine/gui/MainView.java b/src/main/java/brainwine/gui/view/MainView.java similarity index 89% rename from src/main/java/brainwine/gui/MainView.java rename to src/main/java/brainwine/gui/view/MainView.java index 13ac9047..280f8d61 100644 --- a/src/main/java/brainwine/gui/MainView.java +++ b/src/main/java/brainwine/gui/view/MainView.java @@ -1,4 +1,4 @@ -package brainwine.gui; +package brainwine.gui.view; import static brainwine.gui.GuiConstants.DEEPWORLD_PLAYERPREFS; import static brainwine.gui.GuiConstants.GITHUB_REPOSITORY_URL; @@ -26,6 +26,10 @@ import com.formdev.flatlaf.extras.components.FlatTabbedPane.TabAlignment; import brainwine.Main; +import brainwine.gui.GuiPreferences; +import brainwine.gui.component.GamePanel; +import brainwine.gui.component.ServerPanel; +import brainwine.gui.component.SettingsPanel; import brainwine.util.DesktopUtils; import brainwine.util.OperatingSystem; import brainwine.util.ProcessResult; @@ -40,8 +44,6 @@ public class MainView { private final JFrame frame; private final JPanel panel; private final FlatTabbedPane tabbedPane; - private final ServerPanel serverPanel; - private final SettingsPanel settingsPanel; public MainView(Main main) { logger.info(GUI_MARKER, "Creating main view ..."); @@ -53,14 +55,14 @@ public MainView(Main main) { tabbedPane = new FlatTabbedPane(); tabbedPane.setShowContentSeparators(true); tabbedPane.setTabAlignment(TabAlignment.leading); - setTabPlacement(GuiPreferences.getInt(GuiPreferences.TAB_PLACEMENT_KEY, 1), false); + setTabPlacement(GuiPreferences.getTabPlacement(), false); - if(OperatingSystem.isWindows()) { - tabbedPane.addTab("Play Game", UIManager.getIcon("Brainwine.playIcon"), new GamePanel(this)); + if(OperatingSystem.isWindows() || OperatingSystem.isMacOS()) { + tabbedPane.addTab("Play Game", UIManager.getIcon("Brainwine.playIcon"), new GamePanel(main)); } - tabbedPane.addTab("Server", UIManager.getIcon("Brainwine.serverIcon"), serverPanel = new ServerPanel(main)); - tabbedPane.addTab("Settings", UIManager.getIcon("Brainwine.settingsIcon"), settingsPanel = new SettingsPanel(this)); + tabbedPane.addTab("Server", UIManager.getIcon("Brainwine.serverIcon"), new ServerPanel(main)); + tabbedPane.addTab("Settings", UIManager.getIcon("Brainwine.settingsIcon"), new SettingsPanel(this)); panel.add(tabbedPane); // Menu @@ -112,15 +114,6 @@ public int getTabPlacement() { return tabbedPane.getTabPlacement(); } - public void showHostSettings() { - tabbedPane.setSelectedComponent(settingsPanel); - settingsPanel.focusHostSettings(); - } - - public void enableServerButton() { - serverPanel.enableServerButton(); - } - private void showAccountLockPrompt() { int result = JOptionPane.showConfirmDialog(frame, "If you've found yourself in a situation where you are unable to log into an unregistered account" diff --git a/src/main/java/brainwine/gui/view/ProgressView.java b/src/main/java/brainwine/gui/view/ProgressView.java new file mode 100644 index 00000000..713e13c6 --- /dev/null +++ b/src/main/java/brainwine/gui/view/ProgressView.java @@ -0,0 +1,70 @@ +package brainwine.gui.view; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JProgressBar; + +public class ProgressView { + + private final List closeListeners = new ArrayList<>(); + private final JDialog dialog; + private final JLabel label; + private final JProgressBar progressBar; + + public ProgressView(JComponent owner, String text) { + // Create progress bar + progressBar = new JProgressBar(); + progressBar.setBorder(BorderFactory.createEmptyBorder(8, 0, 0, 0)); + progressBar.setMinimumSize(new Dimension(300, 40)); + progressBar.setPreferredSize(progressBar.getMinimumSize()); + progressBar.setStringPainted(true); + + // Create main panel + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); + panel.add(label = new JLabel(text), BorderLayout.PAGE_START); + panel.add(progressBar, BorderLayout.CENTER); + + // Create dialog + JFrame frame = (JFrame)owner.getTopLevelAncestor(); + dialog = new JDialog(frame, "Please wait..."); + dialog.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent event) { + closeListeners.forEach(Runnable::run); + } + }); + dialog.setResizable(false); + dialog.add(panel); + dialog.pack(); + dialog.setLocationRelativeTo(frame); + dialog.setVisible(true); + } + + public void addCloseListener(Runnable listener) { + closeListeners.add(listener); + } + + public void setText(String text) { + label.setText(text); + } + + public void setProgress(int progress) { + progressBar.setValue(progress); + } + + public void dispose() { + dialog.dispose(); + } +} diff --git a/src/main/java/brainwine/patch/BytePatch.java b/src/main/java/brainwine/patch/BytePatch.java new file mode 100644 index 00000000..e97da46d --- /dev/null +++ b/src/main/java/brainwine/patch/BytePatch.java @@ -0,0 +1,41 @@ +package brainwine.patch; + +import java.io.IOException; +import java.io.RandomAccessFile; + +public class BytePatch { + + private final long address; + private final byte from; + private final byte to; + + public BytePatch(long address, int from, int to) { + this.address = address; + this.from = (byte)from; + this.to = (byte)to; + } + + public void apply(RandomAccessFile file) throws IOException { + file.seek(address); + + if(file.readByte() != to) { + file.seek(address); + file.writeByte(to); + } + } + + public void validate(RandomAccessFile file) throws IOException { + if(address < 0 || address >= file.length()) { + throw new IOException(String.format("Address %08X exceeds file range %08X", address, file.length() - 1)); + } + + if(address >= 0 && address < file.length()) { + file.seek(address); + byte b = file.readByte(); + + if(b != from && b != to) { + throw new IOException(String.format("Byte at %08X failed to validate: expected %02X or %02X, got %02X", address, from, to, b)); + } + } + } +} diff --git a/src/main/java/brainwine/patch/DynamicPatch.java b/src/main/java/brainwine/patch/DynamicPatch.java new file mode 100644 index 00000000..c6be3844 --- /dev/null +++ b/src/main/java/brainwine/patch/DynamicPatch.java @@ -0,0 +1,47 @@ +package brainwine.patch; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class DynamicPatch { + + private final Map data = new HashMap<>(); + private final PatchFile patchFile; + + protected DynamicPatch(PatchFile patchFile) { + this.patchFile = patchFile; + } + + protected void apply(RandomAccessFile file) throws IOException { + for(long address : data.keySet()) { + byte[] bytes = data.get(address); + byte[] original = new byte[bytes.length]; + file.seek(address); + file.read(original); + + if(!Arrays.equals(bytes, original)) { + file.seek(address); + file.write(bytes); + } + } + } + + public void setBytes(String name, byte[] bytes) { + long address = patchFile.getAddress(name); + + if(address != -1) { + data.put(address, bytes); + } + } + + public void setInt(String name, int value) { + setBytes(name, new byte[] { + (byte)(value), + (byte)(value >> 8), + (byte)(value >> 16), + (byte)(value >> 24)}); + } +} diff --git a/src/main/java/brainwine/patch/PatchFile.java b/src/main/java/brainwine/patch/PatchFile.java new file mode 100644 index 00000000..e6d53957 --- /dev/null +++ b/src/main/java/brainwine/patch/PatchFile.java @@ -0,0 +1,104 @@ +package brainwine.patch; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.RandomAccessFile; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class PatchFile { + + private final Map addresses = new HashMap<>(); + private final List bytePatches = new ArrayList<>(); + private long binarySize; + + public PatchFile(InputStream inputStream) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + List lines = reader.lines().collect(Collectors.toList()); + + for(String line : lines) { + parseLine(line); + } + } + + private void parseLine(String line) throws IOException { + // Remove excessive whitespace + line = line.trim().replaceAll(" +", " "); + + // Ignore empty lines and comments + if(line.isEmpty() || line.startsWith("#")) { + return; + } + + String[] entry = line.split(" ", 2); + + // Throw exception if entry is just a key with no value + if(entry.length == 1) { + throw new IOException("Entry must be a key followed by a value"); + } + + String key = entry[0]; + String value = entry[1]; + + // Process value based on key + switch(key) { + case "binary_name": + break; + case "binary_size": + binarySize = Long.decode(entry[1]); + break; + case "location": + String[] segments = value.split(" "); + + if(segments.length != 2) { + throw new IOException("Location must be a name followed by an address"); + } + + addresses.put(segments[0], Long.decode(segments[1])); + break; + case "byte_patch": + segments = value.split(" "); + + if(segments.length != 3) { + throw new IOException("Byte patch must be an address followed by the expected and target bytes"); + } + + bytePatches.add(new BytePatch(Long.decode(segments[0]), Integer.decode(segments[1]), Integer.decode(segments[2]))); + break; + default: + throw new IOException(String.format("Unknown key: %s", key)); + } + } + + public void apply(RandomAccessFile file, Consumer propertySetter) throws IOException { + // Validate binary info + if(file.length() != binarySize) { + throw new IOException(String.format("Binary size doesn't match: got %08X, expected %08X", file.length(), binarySize)); + } + + // Validate byte patches + for(BytePatch patch : bytePatches) { + patch.validate(file); + } + + // Apply byte patches + for(BytePatch patch : bytePatches) { + patch.apply(file); + } + + // Apply dynamic patches + DynamicPatch patch = new DynamicPatch(this); + propertySetter.accept(patch); + patch.apply(file); + } + + public long getAddress(String name) { + return addresses.getOrDefault(name, -1L); + } +} diff --git a/src/main/java/brainwine/util/DesktopUtils.java b/src/main/java/brainwine/util/DesktopUtils.java index 364c1b5f..bd01f36e 100644 --- a/src/main/java/brainwine/util/DesktopUtils.java +++ b/src/main/java/brainwine/util/DesktopUtils.java @@ -8,16 +8,19 @@ public class DesktopUtils { - public static void browseUrl(String url) { + public static boolean browseUrl(String url) { Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; if(desktop != null && desktop.isSupported(Action.BROWSE)) { try { URI uri = new URI(url); desktop.browse(uri); + return true; } catch(URISyntaxException | IOException e) { // TODO log this somewhere } } + + return false; } } diff --git a/src/main/java/brainwine/util/SwingUtils.java b/src/main/java/brainwine/util/SwingUtils.java index cd8d0089..527b13f8 100644 --- a/src/main/java/brainwine/util/SwingUtils.java +++ b/src/main/java/brainwine/util/SwingUtils.java @@ -1,14 +1,26 @@ package brainwine.util; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.event.ActionEvent; +import java.io.PrintWriter; +import java.io.StringWriter; import javax.swing.AbstractAction; import javax.swing.Action; +import javax.swing.BorderFactory; import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; import javax.swing.UIManager; import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.extras.components.FlatTextField; public class SwingUtils { @@ -59,13 +71,8 @@ public static GridBagConstraints createConstraints(int x, int y, int width, int return constraints; } - public static void setMenuBarEmbedded(boolean flag) { - UIManager.put("TitlePane.menuBarEmbedded", flag); - FlatLaf.updateUI(); - } - - public static boolean isMenuBarEmbedded() { - return UIManager.getBoolean("TitlePane.menuBarEmbedded"); + public static String getTextFieldValue(FlatTextField textField) { + return textField.getText().isEmpty() ? textField.getPlaceholderText() : textField.getText(); } public static void setDefaultFontSize(int size) { @@ -77,4 +84,29 @@ public static void setDefaultFontSize(int size) { public static int getDefaultFontSize() { return UIManager.getFont("defaultFont").getSize(); } + + public static void showExceptionInfo(Component parent, String message, Throwable throwable) { + // Create stacktrace string + StringWriter writer = new StringWriter(); + throwable.printStackTrace(new PrintWriter(writer)); + + // Create text area + JTextArea area = new JTextArea(writer.toString()); + area.setBorder(BorderFactory.createEmptyBorder(8, 8, 0, 0)); + area.setFont(UIManager.getFont("Brainwine.consoleFont")); + area.setEditable(false); + + // Create scroll pane + JScrollPane scrollPane = new JScrollPane(area); + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + scrollPane.setPreferredSize(new Dimension(600, 200)); + scrollPane.setMaximumSize(scrollPane.getPreferredSize()); + + // Create dialog + String label = String.format("%s
Exception details:

", message); + JPanel panel = new JPanel(new BorderLayout()); + panel.add(new JLabel(label), BorderLayout.PAGE_START); + panel.add(scrollPane); + JOptionPane.showMessageDialog(parent, panel, "An error has occured", JOptionPane.ERROR_MESSAGE); + } } diff --git a/src/main/resources/patches/deepworld-1.13.3.patch b/src/main/resources/patches/deepworld-1.13.3.patch new file mode 100644 index 00000000..1cce3d48 --- /dev/null +++ b/src/main/resources/patches/deepworld-1.13.3.patch @@ -0,0 +1,28 @@ +# Binary info +binary_name Deepworld +binary_size 0x3A4470 + +# Addresses +location gateway_host 0x001A960E +location gateway_host_strlen 0x002BCEF8 + +# Make gateway reachable +byte_patch 0x0002A211 0x74 0xEB + +# Remove code signature +byte_patch 0x00000010 0x22 0x21 +byte_patch 0x00000014 0x78 0x68 +byte_patch 0x00001488 0x1D 0x00 +byte_patch 0x0000148C 0x10 0x00 +byte_patch 0x00001490 0xC0 0x00 +byte_patch 0x00001491 0xD5 0x00 +byte_patch 0x00001492 0x39 0x00 +byte_patch 0x00001494 0xB0 0x00 +byte_patch 0x00001495 0x6E 0x00 +byte_patch 0x0039D5C0 0xFA 0x00 +byte_patch 0x0039D5C1 0xDE 0x00 +byte_patch 0x0039D5C2 0x0C 0x00 +byte_patch 0x0039D5C3 0xC0 0x00 +byte_patch 0x0039D5C6 0x5C 0x00 +byte_patch 0x0039D5C7 0x60 0x00 +byte_patch 0x0039D5CB 0x04 0x00 diff --git a/src/main/resources/patches/deepworld-1.3.10.patch b/src/main/resources/patches/deepworld-1.3.10.patch new file mode 100644 index 00000000..272b8069 --- /dev/null +++ b/src/main/resources/patches/deepworld-1.3.10.patch @@ -0,0 +1,10 @@ +# Binary info +binary_name Deepworld +binary_size 0x3B3FA0 + +# Addresses +location gateway_host 0x0014FC3D +location gateway_host_strlen 0x00228760 + +# Make gateway reachable +byte_patch 0x0002968F 0x74 0xEB diff --git a/src/main/resources/patches/deepworld-2.11.1.patch b/src/main/resources/patches/deepworld-2.11.1.patch new file mode 100644 index 00000000..d85eef12 --- /dev/null +++ b/src/main/resources/patches/deepworld-2.11.1.patch @@ -0,0 +1,29 @@ +# Binary info +binary_name Deepworld +binary_size 0x350010 + +# Addresses +location gateway_host 0x001DAB8D +location gateway_host_strlen 0x00284C60 + +# Make gateway reachable +byte_patch 0x000373AD 0x74 0xEB + +# Remove code signature +byte_patch 0x00000010 0x22 0x21 +byte_patch 0x00000014 0x08 0xF8 +byte_patch 0x00000015 0x14 0x13 +byte_patch 0x00001418 0x1D 0x00 +byte_patch 0x0000141C 0x10 0x00 +byte_patch 0x00001420 0x30 0x00 +byte_patch 0x00001421 0x2E 0x00 +byte_patch 0x00001422 0x34 0x00 +byte_patch 0x00001424 0xE0 0x00 +byte_patch 0x00001425 0xD1 0x00 +byte_patch 0x00342E30 0xFA 0x00 +byte_patch 0x00342E31 0xDE 0x00 +byte_patch 0x00342E32 0x0C 0x00 +byte_patch 0x00342E33 0xC0 0x00 +byte_patch 0x00342E36 0xC0 0x00 +byte_patch 0x00342E37 0xA3 0x00 +byte_patch 0x00342E3B 0x05 0x00 From 24bf61f9c8bb1431596f476d6cf1238c6ef6aa57 Mon Sep 17 00:00:00 2001 From: Kuroppoi Date: Sat, 18 May 2024 16:54:47 +0200 Subject: [PATCH 082/176] Update README.md --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 19006c85..d715894a 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,10 @@ Brainwine currently supports the following versions of Deepworld: - Install [Java 8](https://adoptium.net/temurin/releases/?package=jdk&version=8). - Download the [latest Brainwine release](https://github.com/kuroppoi/brainwine/releases/latest). -- Run Brainwine, go to the server tab and start the server. -- Go to the game tab and start the game. - - If this isn't available for you, download a [patching kit](https://github.com/kuroppoi/brainwine/releases/tag/patching-kits-1.0) for your platform and follow the instructions there. +- Start Brainwine by running the jar file. +- In the window that appears, press "Start Server" to start the server. +- Press "Start Deepworld" to launch the game. + - If you want to play on iOS, download a patching kit for it [here](https://github.com/kuroppoi/brainwine/releases/tag/patching-kits-1.0). - Register a new account and play the game. ## Building From 338e168ae84a6125b540f9fc41f5dbb76ff6ed0d Mon Sep 17 00:00:00 2001 From: Kuroppoi Date: Mon, 20 May 2024 21:27:50 +0200 Subject: [PATCH 083/176] Refactor various things (#54) --- .../gameserver/GameConfiguration.java | 6 +- .../java/brainwine/gameserver/GameServer.java | 10 +- .../Achievement.java | 4 +- .../AchievementManager.java | 2 +- .../CraftingAchievement.java | 6 +- .../DeliveranceAchievement.java | 4 +- .../DiscoveryAchievement.java | 6 +- .../ExploringAchievement.java | 4 +- .../HuntingAchievement.java | 4 +- .../JourneymanAchievement.java | 4 +- .../LazyAchievementGetter.java | 2 +- .../LooterAchievement.java | 4 +- .../MiningAchievement.java | 4 +- .../RaiderAchievement.java | 4 +- .../ScavengingAchievement.java | 6 +- .../SidekickAchievement.java | 4 +- .../SpawnerStoppageAchievement.java | 4 +- .../TrappingAchievement.java | 4 +- .../UndertakerAchievement.java | 4 +- .../{commands => command}/Command.java | 2 +- .../CommandExecutor.java | 4 +- .../{annotations => command}/CommandInfo.java | 2 +- .../{commands => command}/CommandManager.java | 7 +- .../{commands => command}/HelpCommand.java | 6 +- .../RegisterCommand.java | 9 +- .../{commands => command}/SayCommand.java | 9 +- .../{commands => command}/ThinkCommand.java | 9 +- .../admin/AcidityCommand.java | 12 +- .../admin/AdminCommand.java | 12 +- .../admin/BanCommand.java | 12 +- .../admin/BroadcastCommand.java | 14 +- .../admin/EcoCommand.java | 12 +- .../admin/EntityCommand.java | 14 +- .../admin/ExperienceCommand.java | 12 +- .../admin/ExportCommand.java | 24 +-- .../admin/FindCommand.java | 12 +- .../admin/GenerateZoneCommand.java | 10 +- .../admin/GiveCommand.java | 12 +- .../admin/GrowCommand.java | 12 +- .../admin/HealthCommand.java | 12 +- .../admin/ImportCommand.java | 12 +- .../admin/KickCommand.java | 12 +- .../admin/LevelCommand.java | 12 +- .../admin/MuteCommand.java | 12 +- .../admin/PlayerIdCommand.java | 12 +- .../admin/PositionCommand.java | 12 +- .../admin/PrefabListCommand.java | 10 +- .../admin/SeedCommand.java | 12 +- .../admin/SettleLiquidsCommand.java | 12 +- .../admin/SkillPointsCommand.java | 12 +- .../admin/StopCommand.java | 8 +- .../admin/TeleportCommand.java | 12 +- .../admin/TimeCommand.java | 12 +- .../admin/UnbanCommand.java | 12 +- .../admin/UnmuteCommand.java | 12 +- .../admin/WeatherCommand.java | 12 +- .../admin/ZoneIdCommand.java | 12 +- .../brainwine/gameserver/entity/Entity.java | 2 +- .../brainwine/gameserver/entity/npc/Npc.java | 4 +- .../parts/RandomlyTargetBehavior.java | 2 +- .../java/brainwine/gameserver/item/Item.java | 2 +- .../gameserver/item/MiningBonus.java | 2 +- .../item/consumables/Consumable.java | 2 +- .../item/consumables/ConvertConsumable.java | 4 +- .../item/consumables/HealConsumable.java | 2 +- .../consumables/NameChangeConsumable.java | 4 +- .../item/consumables/RefillConsumable.java | 2 +- .../item/consumables/SkillConsumable.java | 4 +- .../consumables/SkillResetConsumable.java | 4 +- .../item/consumables/StealthConsumable.java | 2 +- .../item/consumables/TeleportConsumable.java | 2 +- .../item/interactions/BurstInteraction.java | 4 +- .../item/interactions/ChangeInteraction.java | 2 +- .../interactions/ComposterInteraction.java | 4 +- .../interactions/ContainerInteraction.java | 4 +- .../item/interactions/DialogInteraction.java | 2 +- .../EcologicalMachineInteraction.java | 4 +- .../interactions/ExpiatorInteraction.java | 4 +- .../item/interactions/GeckInteraction.java | 2 +- .../item/interactions/NoteInteraction.java | 4 +- .../interactions/RecyclerInteraction.java | 8 +- .../SpawnTeleportInteraction.java | 2 +- .../item/interactions/SwitchInteraction.java | 2 +- .../TargetTeleportInteraction.java | 2 +- .../interactions/TeleportInteraction.java | 4 +- .../interactions/TransmitInteraction.java | 2 +- .../gameserver/loot/LootManager.java | 23 +-- .../{entity => }/player/Appearance.java | 4 +- .../{entity => }/player/AppearanceSlot.java | 2 +- .../{entity => }/player/ChatType.java | 2 +- .../{entity => }/player/ContainerType.java | 2 +- .../{entity => }/player/Inventory.java | 2 +- .../{entity => }/player/ItemContainer.java | 2 +- .../{entity => }/player/KarmaLevel.java | 2 +- .../{entity => }/player/NameChange.java | 2 +- .../{entity => }/player/NotificationType.java | 2 +- .../{entity => }/player/Placement.java | 2 +- .../{entity => }/player/Player.java | 10 +- .../{entity => }/player/PlayerConfigFile.java | 4 +- .../{entity => }/player/PlayerManager.java | 22 ++- .../player/PlayerRestriction.java | 2 +- .../{entity => }/player/PlayerStatistics.java | 28 +-- .../gameserver/{entity => }/player/Skill.java | 2 +- .../gameserver/prefab/PrefabManager.java | 112 ++++++------ .../gameserver/resource/Resource.java | 34 ++++ .../gameserver/resource/ResourceFinder.java | 172 ++++++++++++++++++ .../serialization/AchievementSerializer.java | 2 +- .../serialization/MessageSerializer.java | 2 +- .../serialization/RequestDeserializer.java | 2 +- .../{annotations => server}/MessageInfo.java | 2 +- .../gameserver/server/NetworkRegistry.java | 3 - .../OptionalField.java | 2 +- .../gameserver/server/PlayerRequest.java | 5 +- .../{annotations => server}/RequestInfo.java | 2 +- .../server/messages/AchievementMessage.java | 2 +- .../messages/AchievementProgressMessage.java | 2 +- .../server/messages/BlockChangeMessage.java | 2 +- .../server/messages/BlockMetaMessage.java | 2 +- .../server/messages/BlocksMessage.java | 2 +- .../server/messages/ChatMessage.java | 4 +- .../server/messages/ConfigurationMessage.java | 2 +- .../server/messages/DialogMessage.java | 2 +- .../server/messages/EffectMessage.java | 2 +- .../server/messages/EntityChangeMessage.java | 2 +- .../server/messages/EntityItemUseMessage.java | 4 +- .../messages/EntityPositionMessage.java | 2 +- .../server/messages/EntityStatusMessage.java | 2 +- .../server/messages/EventMessage.java | 2 +- .../server/messages/HealthMessage.java | 2 +- .../server/messages/HeartbeatMessage.java | 2 +- .../server/messages/InventoryMessage.java | 4 +- .../server/messages/KickMessage.java | 2 +- .../server/messages/LevelMessage.java | 2 +- .../server/messages/LightMessage.java | 2 +- .../server/messages/NotificationMessage.java | 4 +- .../messages/PlayerPositionMessage.java | 2 +- .../server/messages/SkillMessage.java | 4 +- .../server/messages/StatMessage.java | 2 +- .../server/messages/TeleportMessage.java | 2 +- .../server/messages/WardrobeMessage.java | 2 +- .../gameserver/server/messages/XpMessage.java | 2 +- .../server/messages/ZoneExploredMessage.java | 2 +- .../server/messages/ZoneSearchMessage.java | 2 +- .../server/messages/ZoneStatusMessage.java | 2 +- .../server/models/EntityItemUseData.java | 2 +- .../server/pipeline/Connection.java | 6 +- .../server/pipeline/MessageEncoder.java | 2 +- .../server/requests/AdminRequest.java | 8 +- .../server/requests/AuthenticateRequest.java | 10 +- .../server/requests/BlockMineRequest.java | 8 +- .../server/requests/BlockPlaceRequest.java | 6 +- .../server/requests/BlockUseRequest.java | 6 +- .../server/requests/BlocksIgnoreRequest.java | 4 +- .../server/requests/BlocksRequest.java | 4 +- .../requests/ChangeAppearanceRequest.java | 8 +- .../server/requests/ChatRequest.java | 10 +- .../server/requests/ConsoleRequest.java | 6 +- .../server/requests/CraftRequest.java | 10 +- .../server/requests/DialogRequest.java | 8 +- .../server/requests/EntitiesRequest.java | 4 +- .../server/requests/EventRequest.java | 4 +- .../server/requests/HealthRequest.java | 6 +- .../server/requests/HeartbeatRequest.java | 4 +- .../server/requests/HintRequest.java | 4 +- .../server/requests/InventoryMoveRequest.java | 8 +- .../server/requests/InventoryUseRequest.java | 6 +- .../server/requests/MoveRequest.java | 4 +- .../server/requests/RespawnRequest.java | 4 +- .../server/requests/StatusRequest.java | 4 +- .../server/requests/TransactionRequest.java | 4 +- .../server/requests/ZoneChangeRequest.java | 4 +- .../server/requests/ZoneSearchRequest.java | 4 +- .../gameserver/util/ResourceUtils.java | 83 --------- .../gameserver/zone/ChunkManager.java | 2 +- .../gameserver/zone/EntityManager.java | 19 +- .../gameserver/zone/GrowthManager.java | 14 +- .../gameserver/zone/MachineManager.java | 2 +- .../brainwine/gameserver/zone/MetaBlock.java | 2 +- .../java/brainwine/gameserver/zone/Zone.java | 19 +- .../gameserver/zone/ZoneManager.java | 48 +---- .../gameserver/zone/gen/ZoneGenerator.java | 34 ++-- .../{defaults => }/generators/arctic.json | 0 .../{defaults => }/generators/brain.json | 0 .../{defaults => }/generators/deep.json | 0 .../{defaults => }/generators/desert.json | 0 .../{defaults => }/generators/hell.json | 0 .../{defaults => }/generators/plain.json | 0 .../{defaults => }/generators/space.json | 0 .../main/resources/{defaults => }/growth.json | 0 .../resources/{defaults => }/loottables.json | 0 .../dungeons/arctic_surface_1/blocks.dat | Bin .../dungeons/arctic_surface_1/config.json | 0 .../dungeons/generic_large_1/blocks.dat | Bin .../dungeons/generic_large_1/config.json | 0 .../dungeons/generic_large_2/blocks.dat | Bin .../dungeons/generic_large_2/config.json | 0 .../dungeons/generic_large_3/blocks.dat | Bin .../dungeons/generic_large_3/config.json | 0 .../dungeons/generic_large_4/blocks.dat | Bin .../dungeons/generic_large_4/config.json | 0 .../dungeons/generic_large_5/blocks.dat | Bin .../dungeons/generic_large_5/config.json | 0 .../dungeons/generic_large_6/blocks.dat | Bin .../dungeons/generic_large_6/config.json | 0 .../dungeons/generic_medium_1/blocks.dat | Bin .../dungeons/generic_medium_1/config.json | 0 .../dungeons/generic_medium_10/blocks.dat | Bin .../dungeons/generic_medium_10/config.json | 0 .../dungeons/generic_medium_11/blocks.dat | Bin .../dungeons/generic_medium_11/config.json | 0 .../dungeons/generic_medium_2/blocks.dat | Bin .../dungeons/generic_medium_2/config.json | 0 .../dungeons/generic_medium_3/blocks.dat | Bin .../dungeons/generic_medium_3/config.json | 0 .../dungeons/generic_medium_4/blocks.dat | Bin .../dungeons/generic_medium_4/config.json | 0 .../dungeons/generic_medium_5/blocks.dat | Bin .../dungeons/generic_medium_5/config.json | 0 .../dungeons/generic_medium_6/blocks.dat | Bin .../dungeons/generic_medium_6/config.json | 0 .../dungeons/generic_medium_7/blocks.dat | Bin .../dungeons/generic_medium_7/config.json | 0 .../dungeons/generic_medium_8/blocks.dat | Bin .../dungeons/generic_medium_8/config.json | 0 .../dungeons/generic_medium_9/blocks.dat | Bin .../dungeons/generic_medium_9/config.json | 0 .../dungeons/generic_small_1/blocks.dat | 0 .../dungeons/generic_small_1/config.json | 0 .../dungeons/generic_small_2/blocks.dat | 0 .../dungeons/generic_small_2/config.json | 0 .../dungeons/generic_small_3/blocks.dat | Bin .../dungeons/generic_small_3/config.json | 0 .../dungeons/generic_small_4/blocks.dat | Bin .../dungeons/generic_small_4/config.json | 0 .../dungeons/generic_small_5/blocks.dat | Bin .../dungeons/generic_small_5/config.json | 0 .../dungeons/generic_small_6/blocks.dat | Bin .../dungeons/generic_small_6/config.json | 0 .../dungeons/generic_small_7/blocks.dat | Bin .../dungeons/generic_small_7/config.json | 0 .../dungeons/generic_small_8/blocks.dat | Bin .../dungeons/generic_small_8/config.json | 0 .../dungeons/generic_small_9/blocks.dat | Bin .../dungeons/generic_small_9/config.json | 0 .../prefabs/dungeons/hell_small_1/blocks.dat | Bin .../prefabs/dungeons/hell_small_1/config.json | 0 .../dungeons/hell_surface_1/blocks.dat | 0 .../dungeons/hell_surface_1/config.json | 0 .../prefabs/misc/bucket_shrine/blocks.dat | Bin .../prefabs/misc/bucket_shrine/config.json | 0 .../prefabs/misc/gift_bunker_brass/blocks.dat | Bin .../misc/gift_bunker_brass/config.json | 0 .../prefabs/misc/gift_bunker_iron/blocks.dat | Bin .../prefabs/misc/gift_bunker_iron/config.json | 0 .../prefabs/misc/gift_bunker_wood/blocks.dat | Bin .../prefabs/misc/gift_bunker_wood/config.json | 0 .../prefabs/misc/head_bunker/blocks.dat | Bin .../prefabs/misc/head_bunker/config.json | 0 .../prefabs/misc/luxury_bunker/blocks.dat | Bin .../prefabs/misc/luxury_bunker/config.json | 0 .../prefabs/misc/machine_bunker/blocks.dat | Bin .../prefabs/misc/machine_bunker/config.json | 0 .../prefabs/misc/music_bunker/blocks.dat | Bin .../prefabs/misc/music_bunker/config.json | 0 .../prefabs/misc/painting_bunker/blocks.dat | Bin .../prefabs/misc/painting_bunker/config.json | 0 .../misc/sculpture_bunker_double/blocks.dat | Bin .../misc/sculpture_bunker_double/config.json | 0 .../misc/sculpture_bunker_single/blocks.dat | Bin .../misc/sculpture_bunker_single/config.json | 0 .../arctic_surface_building_1/blocks.dat | Bin .../arctic_surface_building_1/config.json | 0 .../arctic_surface_building_2/blocks.dat | Bin .../arctic_surface_building_2/config.json | 0 .../arctic_surface_building_3/blocks.dat | Bin .../arctic_surface_building_3/config.json | 0 .../arctic_surface_building_4/blocks.dat | Bin .../arctic_surface_building_4/config.json | 0 .../arctic_surface_building_5/blocks.dat | Bin .../arctic_surface_building_5/config.json | 0 .../arctic_surface_building_6/blocks.dat | Bin .../arctic_surface_building_6/config.json | 0 .../arctic_surface_building_7/blocks.dat | Bin .../arctic_surface_building_7/config.json | 0 .../arctic_surface_building_8/blocks.dat | Bin .../arctic_surface_building_8/config.json | 0 .../arctic_surface_building_9/blocks.dat | Bin .../arctic_surface_building_9/config.json | 0 .../desert_surface_building_1/blocks.dat | Bin .../desert_surface_building_1/config.json | 0 .../desert_surface_building_2/blocks.dat | Bin .../desert_surface_building_2/config.json | 0 .../desert_surface_building_3/blocks.dat | Bin .../desert_surface_building_3/config.json | 0 .../desert_surface_building_4/blocks.dat | Bin .../desert_surface_building_4/config.json | 0 .../ruins/generic_buried_house_1/blocks.dat | Bin .../ruins/generic_buried_house_1/config.json | 0 .../ruins/generic_buried_house_2/blocks.dat | Bin .../ruins/generic_buried_house_2/config.json | 0 .../ruins/generic_buried_house_3/blocks.dat | Bin .../ruins/generic_buried_house_3/config.json | 0 .../prefabs/ruins/generic_metal_1/blocks.dat | Bin .../prefabs/ruins/generic_metal_1/config.json | 0 .../prefabs/ruins/generic_metal_2/blocks.dat | Bin .../prefabs/ruins/generic_metal_2/config.json | 0 .../prefabs/ruins/generic_metal_3/blocks.dat | Bin .../prefabs/ruins/generic_metal_3/config.json | 0 .../prefabs/ruins/generic_metal_4/blocks.dat | Bin .../prefabs/ruins/generic_metal_4/config.json | 0 .../prefabs/ruins/generic_stone_1/blocks.dat | Bin .../prefabs/ruins/generic_stone_1/config.json | 0 .../prefabs/ruins/generic_wood_1/blocks.dat | Bin .../prefabs/ruins/generic_wood_1/config.json | 0 .../prefabs/ruins/generic_wood_2/blocks.dat | Bin .../prefabs/ruins/generic_wood_2/config.json | 0 .../prefabs/ruins/generic_wood_3/blocks.dat | Bin .../prefabs/ruins/generic_wood_3/config.json | 0 .../prefabs/ruins/generic_wood_4/blocks.dat | Bin .../prefabs/ruins/generic_wood_4/config.json | 0 .../prefabs/ruins/generic_wood_5/blocks.dat | Bin .../prefabs/ruins/generic_wood_5/config.json | 0 .../ruins/hell_surface_building_1/blocks.dat | Bin .../ruins/hell_surface_building_1/config.json | 0 .../ruins/hell_surface_building_2/blocks.dat | Bin .../ruins/hell_surface_building_2/config.json | 0 .../ruins/hell_surface_building_3/blocks.dat | Bin .../ruins/hell_surface_building_3/config.json | 0 .../ruins/hell_surface_building_4/blocks.dat | Bin .../ruins/hell_surface_building_4/config.json | 0 .../ruins/hell_surface_building_5/blocks.dat | Bin .../ruins/hell_surface_building_5/config.json | 0 .../ruins/hell_surface_building_6/blocks.dat | Bin .../ruins/hell_surface_building_6/config.json | 0 .../ruins/hell_surface_building_7/blocks.dat | Bin .../ruins/hell_surface_building_7/config.json | 0 .../ruins/plain_surface_building_1/blocks.dat | Bin .../plain_surface_building_1/config.json | 0 .../plain_surface_building_10/blocks.dat | Bin .../plain_surface_building_10/config.json | 0 .../ruins/plain_surface_building_2/blocks.dat | Bin .../plain_surface_building_2/config.json | 0 .../ruins/plain_surface_building_3/blocks.dat | Bin .../plain_surface_building_3/config.json | 0 .../ruins/plain_surface_building_4/blocks.dat | Bin .../plain_surface_building_4/config.json | 0 .../ruins/plain_surface_building_5/blocks.dat | Bin .../plain_surface_building_5/config.json | 0 .../ruins/plain_surface_building_6/blocks.dat | Bin .../plain_surface_building_6/config.json | 0 .../ruins/plain_surface_building_7/blocks.dat | Bin .../plain_surface_building_7/config.json | 0 .../ruins/plain_surface_building_8/blocks.dat | Bin .../plain_surface_building_8/config.json | 0 .../ruins/plain_surface_building_9/blocks.dat | Bin .../plain_surface_building_9/config.json | 0 .../prefabs/spawns/arctic_1/blocks.dat | Bin .../prefabs/spawns/arctic_1/config.json | 0 .../prefabs/spawns/arctic_2/blocks.dat | Bin .../prefabs/spawns/arctic_2/config.json | 0 .../prefabs/spawns/arctic_3/blocks.dat | Bin .../prefabs/spawns/arctic_3/config.json | 0 .../prefabs/spawns/brain_1/blocks.dat | Bin .../prefabs/spawns/brain_1/config.json | 0 .../prefabs/spawns/deep_1/blocks.dat | Bin .../prefabs/spawns/deep_1/config.json | 0 .../prefabs/spawns/desert_1/blocks.dat | Bin .../prefabs/spawns/desert_1/config.json | 0 .../prefabs/spawns/desert_2/blocks.dat | Bin .../prefabs/spawns/desert_2/config.json | 0 .../prefabs/spawns/hell_1/blocks.dat | Bin .../prefabs/spawns/hell_1/config.json | 0 .../prefabs/spawns/hell_2/blocks.dat | Bin .../prefabs/spawns/hell_2/config.json | 0 .../prefabs/spawns/hell_3/blocks.dat | Bin .../prefabs/spawns/hell_3/config.json | 0 .../prefabs/spawns/plain_1/blocks.dat | Bin .../prefabs/spawns/plain_1/config.json | 0 .../prefabs/spawns/plain_2/blocks.dat | Bin .../prefabs/spawns/plain_2/config.json | 0 .../prefabs/spawns/plain_3/blocks.dat | Bin .../prefabs/spawns/plain_3/config.json | 0 .../prefabs/spawns/plain_4/blocks.dat | Bin .../prefabs/spawns/plain_4/config.json | 0 .../prefabs/spawns/space_1/blocks.dat | Bin .../prefabs/spawns/space_1/config.json | 0 .../resources/{defaults => }/spawning.json | 0 .../java/brainwine/shared/JsonHelper.java | 9 + .../java/brainwine/DirectDataFetcher.java | 4 +- src/main/java/brainwine/ServerThread.java | 2 +- 390 files changed, 792 insertions(+), 715 deletions(-) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/Achievement.java (97%) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/AchievementManager.java (98%) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/CraftingAchievement.java (88%) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/DeliveranceAchievement.java (81%) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/DiscoveryAchievement.java (88%) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/ExploringAchievement.java (81%) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/HuntingAchievement.java (89%) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/JourneymanAchievement.java (80%) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/LazyAchievementGetter.java (87%) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/LooterAchievement.java (81%) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/MiningAchievement.java (89%) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/RaiderAchievement.java (81%) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/ScavengingAchievement.java (87%) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/SidekickAchievement.java (81%) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/SpawnerStoppageAchievement.java (82%) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/TrappingAchievement.java (81%) rename gameserver/src/main/java/brainwine/gameserver/{achievements => achievement}/UndertakerAchievement.java (81%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/Command.java (87%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/CommandExecutor.java (58%) rename gameserver/src/main/java/brainwine/gameserver/{annotations => command}/CommandInfo.java (89%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/CommandManager.java (96%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/HelpCommand.java (94%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/RegisterCommand.java (90%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/SayCommand.java (78%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/ThinkCommand.java (78%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/AcidityCommand.java (81%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/AdminCommand.java (83%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/BanCommand.java (87%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/BroadcastCommand.java (71%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/EcoCommand.java (91%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/EntityCommand.java (71%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/ExperienceCommand.java (85%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/ExportCommand.java (81%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/FindCommand.java (78%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/GenerateZoneCommand.java (93%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/GiveCommand.java (90%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/GrowCommand.java (79%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/HealthCommand.java (84%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/ImportCommand.java (84%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/KickCommand.java (80%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/LevelCommand.java (86%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/MuteCommand.java (87%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/PlayerIdCommand.java (78%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/PositionCommand.java (68%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/PrefabListCommand.java (84%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/SeedCommand.java (79%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/SettleLiquidsCommand.java (73%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/SkillPointsCommand.java (86%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/StopCommand.java (74%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/TeleportCommand.java (78%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/TimeCommand.java (84%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/UnbanCommand.java (79%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/UnmuteCommand.java (78%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/WeatherCommand.java (83%) rename gameserver/src/main/java/brainwine/gameserver/{commands => command}/admin/ZoneIdCommand.java (79%) rename gameserver/src/main/java/brainwine/gameserver/{entity => }/player/Appearance.java (97%) rename gameserver/src/main/java/brainwine/gameserver/{entity => }/player/AppearanceSlot.java (97%) rename gameserver/src/main/java/brainwine/gameserver/{entity => }/player/ChatType.java (88%) rename gameserver/src/main/java/brainwine/gameserver/{entity => }/player/ContainerType.java (88%) rename gameserver/src/main/java/brainwine/gameserver/{entity => }/player/Inventory.java (99%) rename gameserver/src/main/java/brainwine/gameserver/{entity => }/player/ItemContainer.java (96%) rename gameserver/src/main/java/brainwine/gameserver/{entity => }/player/KarmaLevel.java (93%) rename gameserver/src/main/java/brainwine/gameserver/{entity => }/player/NameChange.java (94%) rename gameserver/src/main/java/brainwine/gameserver/{entity => }/player/NotificationType.java (93%) rename gameserver/src/main/java/brainwine/gameserver/{entity => }/player/Placement.java (92%) rename gameserver/src/main/java/brainwine/gameserver/{entity => }/player/Player.java (99%) rename gameserver/src/main/java/brainwine/gameserver/{entity => }/player/PlayerConfigFile.java (98%) rename gameserver/src/main/java/brainwine/gameserver/{entity => }/player/PlayerManager.java (93%) rename gameserver/src/main/java/brainwine/gameserver/{entity => }/player/PlayerRestriction.java (97%) rename gameserver/src/main/java/brainwine/gameserver/{entity => }/player/PlayerStatistics.java (93%) rename gameserver/src/main/java/brainwine/gameserver/{entity => }/player/Skill.java (94%) create mode 100644 gameserver/src/main/java/brainwine/gameserver/resource/Resource.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/resource/ResourceFinder.java rename gameserver/src/main/java/brainwine/gameserver/{annotations => server}/MessageInfo.java (97%) rename gameserver/src/main/java/brainwine/gameserver/{annotations => server}/OptionalField.java (86%) rename gameserver/src/main/java/brainwine/gameserver/{annotations => server}/RequestInfo.java (87%) delete mode 100644 gameserver/src/main/java/brainwine/gameserver/util/ResourceUtils.java rename gameserver/src/main/resources/{defaults => }/generators/arctic.json (100%) rename gameserver/src/main/resources/{defaults => }/generators/brain.json (100%) rename gameserver/src/main/resources/{defaults => }/generators/deep.json (100%) rename gameserver/src/main/resources/{defaults => }/generators/desert.json (100%) rename gameserver/src/main/resources/{defaults => }/generators/hell.json (100%) rename gameserver/src/main/resources/{defaults => }/generators/plain.json (100%) rename gameserver/src/main/resources/{defaults => }/generators/space.json (100%) rename gameserver/src/main/resources/{defaults => }/growth.json (100%) rename gameserver/src/main/resources/{defaults => }/loottables.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/arctic_surface_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/arctic_surface_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_large_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_large_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_large_2/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_large_2/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_large_3/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_large_3/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_large_4/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_large_4/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_large_5/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_large_5/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_large_6/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_large_6/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_10/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_10/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_11/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_11/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_2/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_2/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_3/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_3/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_4/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_4/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_5/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_5/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_6/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_6/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_7/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_7/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_8/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_8/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_9/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_medium_9/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_2/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_2/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_3/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_3/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_4/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_4/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_5/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_5/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_6/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_6/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_7/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_7/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_8/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_8/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_9/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/generic_small_9/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/hell_small_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/hell_small_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/hell_surface_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/dungeons/hell_surface_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/bucket_shrine/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/bucket_shrine/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/gift_bunker_brass/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/gift_bunker_brass/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/gift_bunker_iron/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/gift_bunker_iron/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/gift_bunker_wood/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/gift_bunker_wood/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/head_bunker/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/head_bunker/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/luxury_bunker/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/luxury_bunker/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/machine_bunker/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/machine_bunker/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/music_bunker/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/music_bunker/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/painting_bunker/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/painting_bunker/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/sculpture_bunker_double/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/sculpture_bunker_double/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/sculpture_bunker_single/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/misc/sculpture_bunker_single/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_2/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_2/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_3/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_3/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_4/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_4/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_5/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_5/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_6/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_6/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_7/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_7/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_8/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_8/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_9/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/arctic_surface_building_9/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/desert_surface_building_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/desert_surface_building_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/desert_surface_building_2/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/desert_surface_building_2/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/desert_surface_building_3/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/desert_surface_building_3/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/desert_surface_building_4/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/desert_surface_building_4/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_buried_house_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_buried_house_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_buried_house_2/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_buried_house_2/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_buried_house_3/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_buried_house_3/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_metal_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_metal_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_metal_2/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_metal_2/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_metal_3/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_metal_3/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_metal_4/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_metal_4/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_stone_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_stone_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_wood_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_wood_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_wood_2/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_wood_2/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_wood_3/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_wood_3/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_wood_4/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_wood_4/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_wood_5/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/generic_wood_5/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/hell_surface_building_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/hell_surface_building_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/hell_surface_building_2/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/hell_surface_building_2/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/hell_surface_building_3/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/hell_surface_building_3/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/hell_surface_building_4/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/hell_surface_building_4/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/hell_surface_building_5/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/hell_surface_building_5/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/hell_surface_building_6/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/hell_surface_building_6/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/hell_surface_building_7/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/hell_surface_building_7/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_10/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_10/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_2/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_2/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_3/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_3/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_4/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_4/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_5/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_5/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_6/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_6/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_7/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_7/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_8/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_8/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_9/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/ruins/plain_surface_building_9/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/arctic_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/arctic_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/arctic_2/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/arctic_2/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/arctic_3/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/arctic_3/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/brain_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/brain_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/deep_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/deep_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/desert_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/desert_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/desert_2/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/desert_2/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/hell_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/hell_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/hell_2/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/hell_2/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/hell_3/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/hell_3/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/plain_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/plain_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/plain_2/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/plain_2/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/plain_3/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/plain_3/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/plain_4/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/plain_4/config.json (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/space_1/blocks.dat (100%) rename gameserver/src/main/resources/{defaults => }/prefabs/spawns/space_1/config.json (100%) rename gameserver/src/main/resources/{defaults => }/spawning.json (100%) diff --git a/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java b/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java index 6dbc0a81..fe527439 100644 --- a/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java +++ b/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java @@ -22,11 +22,11 @@ import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; -import brainwine.gameserver.commands.CommandManager; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.Skill; +import brainwine.gameserver.command.CommandManager; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.Skill; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.VersionUtils; import brainwine.shared.JsonHelper; diff --git a/gameserver/src/main/java/brainwine/gameserver/GameServer.java b/gameserver/src/main/java/brainwine/gameserver/GameServer.java index 4585e0e0..b5f804fa 100644 --- a/gameserver/src/main/java/brainwine/gameserver/GameServer.java +++ b/gameserver/src/main/java/brainwine/gameserver/GameServer.java @@ -8,13 +8,13 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import brainwine.gameserver.achievements.AchievementManager; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.commands.CommandManager; +import brainwine.gameserver.achievement.AchievementManager; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandManager; import brainwine.gameserver.entity.EntityRegistry; -import brainwine.gameserver.entity.player.NotificationType; -import brainwine.gameserver.entity.player.PlayerManager; import brainwine.gameserver.loot.LootManager; +import brainwine.gameserver.player.NotificationType; +import brainwine.gameserver.player.PlayerManager; import brainwine.gameserver.prefab.PrefabManager; import brainwine.gameserver.server.NetworkRegistry; import brainwine.gameserver.server.Server; diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/Achievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/Achievement.java similarity index 97% rename from gameserver/src/main/java/brainwine/gameserver/achievements/Achievement.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/Achievement.java index 2595daf9..a643e901 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/Achievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/Achievement.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator.Mode; @@ -12,7 +12,7 @@ import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.serialization.AchievementSerializer; import brainwine.gameserver.util.MathUtils; diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/AchievementManager.java b/gameserver/src/main/java/brainwine/gameserver/achievement/AchievementManager.java similarity index 98% rename from gameserver/src/main/java/brainwine/gameserver/achievements/AchievementManager.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/AchievementManager.java index ede955f9..4515fc1d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/AchievementManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/AchievementManager.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import static brainwine.shared.LogMarkers.SERVER_MARKER; diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/CraftingAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/CraftingAchievement.java similarity index 88% rename from gameserver/src/main/java/brainwine/gameserver/achievements/CraftingAchievement.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/CraftingAchievement.java index 450dbd32..ecf6ba8a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/CraftingAchievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/CraftingAchievement.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import java.util.List; @@ -6,9 +6,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.PlayerStatistics; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.PlayerStatistics; public class CraftingAchievement extends Achievement { diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/DeliveranceAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/DeliveranceAchievement.java similarity index 81% rename from gameserver/src/main/java/brainwine/gameserver/achievements/DeliveranceAchievement.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/DeliveranceAchievement.java index 7e19070d..0f01df73 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/DeliveranceAchievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/DeliveranceAchievement.java @@ -1,9 +1,9 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; public class DeliveranceAchievement extends Achievement { diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/DiscoveryAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/DiscoveryAchievement.java similarity index 88% rename from gameserver/src/main/java/brainwine/gameserver/achievements/DiscoveryAchievement.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/DiscoveryAchievement.java index ddccda34..d1e9efde 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/DiscoveryAchievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/DiscoveryAchievement.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import java.util.Map.Entry; @@ -6,10 +6,10 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.PlayerStatistics; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemGroup; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.PlayerStatistics; public class DiscoveryAchievement extends Achievement { diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/ExploringAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/ExploringAchievement.java similarity index 81% rename from gameserver/src/main/java/brainwine/gameserver/achievements/ExploringAchievement.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/ExploringAchievement.java index 995d7b34..57f97018 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/ExploringAchievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/ExploringAchievement.java @@ -1,9 +1,9 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; public class ExploringAchievement extends Achievement { diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/HuntingAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/HuntingAchievement.java similarity index 89% rename from gameserver/src/main/java/brainwine/gameserver/achievements/HuntingAchievement.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/HuntingAchievement.java index 04d26f06..c2b0faeb 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/HuntingAchievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/HuntingAchievement.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import java.util.Map.Entry; @@ -7,7 +7,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import brainwine.gameserver.entity.EntityGroup; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; public class HuntingAchievement extends Achievement { diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/JourneymanAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/JourneymanAchievement.java similarity index 80% rename from gameserver/src/main/java/brainwine/gameserver/achievements/JourneymanAchievement.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/JourneymanAchievement.java index f3a8b3d0..c0f48aeb 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/JourneymanAchievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/JourneymanAchievement.java @@ -1,9 +1,9 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; public class JourneymanAchievement extends Achievement { diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/LazyAchievementGetter.java b/gameserver/src/main/java/brainwine/gameserver/achievement/LazyAchievementGetter.java similarity index 87% rename from gameserver/src/main/java/brainwine/gameserver/achievements/LazyAchievementGetter.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/LazyAchievementGetter.java index 685413a8..8a12c843 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/LazyAchievementGetter.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/LazyAchievementGetter.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import brainwine.gameserver.util.LazyGetter; diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/LooterAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/LooterAchievement.java similarity index 81% rename from gameserver/src/main/java/brainwine/gameserver/achievements/LooterAchievement.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/LooterAchievement.java index e08c6ade..06e9732d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/LooterAchievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/LooterAchievement.java @@ -1,9 +1,9 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; public class LooterAchievement extends Achievement { diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/MiningAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/MiningAchievement.java similarity index 89% rename from gameserver/src/main/java/brainwine/gameserver/achievements/MiningAchievement.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/MiningAchievement.java index 8ea7b139..b5443189 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/MiningAchievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/MiningAchievement.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import java.util.Map.Entry; @@ -6,8 +6,8 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.ItemGroup; +import brainwine.gameserver.player.Player; public class MiningAchievement extends Achievement { diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/RaiderAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/RaiderAchievement.java similarity index 81% rename from gameserver/src/main/java/brainwine/gameserver/achievements/RaiderAchievement.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/RaiderAchievement.java index 37581cc6..07777e60 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/RaiderAchievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/RaiderAchievement.java @@ -1,9 +1,9 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; public class RaiderAchievement extends Achievement { diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/ScavengingAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/ScavengingAchievement.java similarity index 87% rename from gameserver/src/main/java/brainwine/gameserver/achievements/ScavengingAchievement.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/ScavengingAchievement.java index a5a394d9..e7331d50 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/ScavengingAchievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/ScavengingAchievement.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import java.util.List; @@ -6,9 +6,9 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.PlayerStatistics; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.PlayerStatistics; public class ScavengingAchievement extends Achievement { diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/SidekickAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/SidekickAchievement.java similarity index 81% rename from gameserver/src/main/java/brainwine/gameserver/achievements/SidekickAchievement.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/SidekickAchievement.java index f5442475..3ffbec4c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/SidekickAchievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/SidekickAchievement.java @@ -1,9 +1,9 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; public class SidekickAchievement extends Achievement { diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/SpawnerStoppageAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/SpawnerStoppageAchievement.java similarity index 82% rename from gameserver/src/main/java/brainwine/gameserver/achievements/SpawnerStoppageAchievement.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/SpawnerStoppageAchievement.java index ac3b3b0a..dc358259 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/SpawnerStoppageAchievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/SpawnerStoppageAchievement.java @@ -1,9 +1,9 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; public class SpawnerStoppageAchievement extends Achievement { diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/TrappingAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/TrappingAchievement.java similarity index 81% rename from gameserver/src/main/java/brainwine/gameserver/achievements/TrappingAchievement.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/TrappingAchievement.java index 72cf2b2b..7dec1553 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/TrappingAchievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/TrappingAchievement.java @@ -1,9 +1,9 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; public class TrappingAchievement extends Achievement { diff --git a/gameserver/src/main/java/brainwine/gameserver/achievements/UndertakerAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/UndertakerAchievement.java similarity index 81% rename from gameserver/src/main/java/brainwine/gameserver/achievements/UndertakerAchievement.java rename to gameserver/src/main/java/brainwine/gameserver/achievement/UndertakerAchievement.java index 591965d1..c86856db 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievements/UndertakerAchievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/UndertakerAchievement.java @@ -1,9 +1,9 @@ -package brainwine.gameserver.achievements; +package brainwine.gameserver.achievement; import com.fasterxml.jackson.annotation.JacksonInject; import com.fasterxml.jackson.annotation.JsonCreator; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; public class UndertakerAchievement extends Achievement { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/Command.java b/gameserver/src/main/java/brainwine/gameserver/command/Command.java similarity index 87% rename from gameserver/src/main/java/brainwine/gameserver/commands/Command.java rename to gameserver/src/main/java/brainwine/gameserver/command/Command.java index c6555970..ddf30223 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/Command.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/Command.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.commands; +package brainwine.gameserver.command; public abstract class Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/CommandExecutor.java b/gameserver/src/main/java/brainwine/gameserver/command/CommandExecutor.java similarity index 58% rename from gameserver/src/main/java/brainwine/gameserver/commands/CommandExecutor.java rename to gameserver/src/main/java/brainwine/gameserver/command/CommandExecutor.java index cda63e79..77b05758 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/CommandExecutor.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/CommandExecutor.java @@ -1,6 +1,6 @@ -package brainwine.gameserver.commands; +package brainwine.gameserver.command; -import brainwine.gameserver.entity.player.NotificationType; +import brainwine.gameserver.player.NotificationType; public interface CommandExecutor { diff --git a/gameserver/src/main/java/brainwine/gameserver/annotations/CommandInfo.java b/gameserver/src/main/java/brainwine/gameserver/command/CommandInfo.java similarity index 89% rename from gameserver/src/main/java/brainwine/gameserver/annotations/CommandInfo.java rename to gameserver/src/main/java/brainwine/gameserver/command/CommandInfo.java index 9dd4d137..f723865c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/annotations/CommandInfo.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/CommandInfo.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.annotations; +package brainwine.gameserver.command; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/CommandManager.java b/gameserver/src/main/java/brainwine/gameserver/command/CommandManager.java similarity index 96% rename from gameserver/src/main/java/brainwine/gameserver/commands/CommandManager.java rename to gameserver/src/main/java/brainwine/gameserver/command/CommandManager.java index 3f80e75a..d3a2b994 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/CommandManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/CommandManager.java @@ -1,6 +1,6 @@ -package brainwine.gameserver.commands; +package brainwine.gameserver.command; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import static brainwine.shared.LogMarkers.SERVER_MARKER; import java.util.Collection; @@ -17,8 +17,7 @@ import org.apache.logging.log4j.Logger; import org.reflections.Reflections; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; @SuppressWarnings("unchecked") public class CommandManager { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/HelpCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/HelpCommand.java similarity index 94% rename from gameserver/src/main/java/brainwine/gameserver/commands/HelpCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/HelpCommand.java index 8c9fcb31..325ee39e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/HelpCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/HelpCommand.java @@ -1,6 +1,6 @@ -package brainwine.gameserver.commands; +package brainwine.gameserver.command; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import java.util.ArrayList; import java.util.Arrays; @@ -8,8 +8,6 @@ import org.apache.commons.lang3.math.NumberUtils; -import brainwine.gameserver.annotations.CommandInfo; - @CommandInfo(name = "help", description = "Displays a list of commands.") public class HelpCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/RegisterCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/RegisterCommand.java similarity index 90% rename from gameserver/src/main/java/brainwine/gameserver/commands/RegisterCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/RegisterCommand.java index cab3cf59..5c1f8679 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/RegisterCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/RegisterCommand.java @@ -1,14 +1,13 @@ -package brainwine.gameserver.commands; +package brainwine.gameserver.command; + +import static brainwine.gameserver.player.NotificationType.SYSTEM; import org.apache.commons.validator.routines.EmailValidator; import org.mindrot.jbcrypt.BCrypt; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; import brainwine.gameserver.dialog.DialogHelper; -import brainwine.gameserver.entity.player.Player; - -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import brainwine.gameserver.player.Player; @CommandInfo(name = "register", description = "Shows a prompt with which you can register your account.") public class RegisterCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/SayCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/SayCommand.java similarity index 78% rename from gameserver/src/main/java/brainwine/gameserver/commands/SayCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/SayCommand.java index 63f82d50..c41fec0e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/SayCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/SayCommand.java @@ -1,10 +1,9 @@ -package brainwine.gameserver.commands; +package brainwine.gameserver.command; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.entity.player.ChatType; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.ChatType; +import brainwine.gameserver.player.Player; @CommandInfo(name = "say", description = "Shows a speech bubble to nearby players.") public class SayCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/ThinkCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/ThinkCommand.java similarity index 78% rename from gameserver/src/main/java/brainwine/gameserver/commands/ThinkCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/ThinkCommand.java index 599aa0a9..2c5f9e01 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/ThinkCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/ThinkCommand.java @@ -1,10 +1,9 @@ -package brainwine.gameserver.commands; +package brainwine.gameserver.command; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.entity.player.ChatType; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.ChatType; +import brainwine.gameserver.player.Player; @CommandInfo(name = "think", description = "Shows a thought bubble to nearby players.") public class ThinkCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/AcidityCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/AcidityCommand.java similarity index 81% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/AcidityCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/AcidityCommand.java index 2bfc0923..e66bea1a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/AcidityCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/AcidityCommand.java @@ -1,11 +1,11 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.Zone; @CommandInfo(name = "acidity", description = "Displays or changes the acidity in the current zone.") diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/AdminCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/AdminCommand.java similarity index 83% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/AdminCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/AdminCommand.java index 7106f5fb..e93f6f9e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/AdminCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/AdminCommand.java @@ -1,12 +1,12 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; @CommandInfo(name = "admin", description = "Grants or revokes administrator rights.") public class AdminCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/BanCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/BanCommand.java similarity index 87% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/BanCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/BanCommand.java index d9982db5..ebdc8fd1 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/BanCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/BanCommand.java @@ -1,16 +1,16 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.Arrays; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; import brainwine.gameserver.util.DateTimeUtils; @CommandInfo(name = "ban", description = "Bans a player from the server.") diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/BroadcastCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/BroadcastCommand.java similarity index 71% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/BroadcastCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/BroadcastCommand.java index 1bb7b253..3f8d7e5a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/BroadcastCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/BroadcastCommand.java @@ -1,13 +1,13 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.POPUP; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.POPUP; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; @CommandInfo(name = "broadcast", description = "Broadcasts a message to all online players.", aliases = "bc") public class BroadcastCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/EcoCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/EcoCommand.java similarity index 91% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/EcoCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/EcoCommand.java index 85a7e75e..c1ca1c19 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/EcoCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/EcoCommand.java @@ -1,16 +1,16 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import java.util.Arrays; import java.util.Collection; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; +import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.EcologicalMachine; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/EntityCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/EntityCommand.java similarity index 71% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/EntityCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/EntityCommand.java index 701e240b..b9281b0c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/EntityCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/EntityCommand.java @@ -1,12 +1,12 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.NotificationType; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.NotificationType; +import brainwine.gameserver.player.Player; @CommandInfo(name = "entity", description = "Spawns an entity at your current location.") public class EntityCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/ExperienceCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/ExperienceCommand.java similarity index 85% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/ExperienceCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/ExperienceCommand.java index fdd84d7f..c4329ed7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/ExperienceCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/ExperienceCommand.java @@ -1,12 +1,12 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; @CommandInfo(name = "experience", description = "Sets the experience of the target player.", aliases = { "xp", "exp" }) public class ExperienceCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/ExportCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/ExportCommand.java similarity index 81% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/ExportCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/ExportCommand.java index 399f1f48..21e4f6cf 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/ExportCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/ExportCommand.java @@ -1,15 +1,14 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; -import java.util.Arrays; import java.util.regex.Pattern; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; import brainwine.gameserver.prefab.Prefab; import brainwine.gameserver.prefab.PrefabManager; import brainwine.gameserver.zone.Zone; @@ -27,12 +26,13 @@ public void execute(CommandExecutor executor, String[] args) { return; } + boolean overwrite = args.length >= 6 && args[5].equalsIgnoreCase("overwrite"); Zone zone = ((Player)executor).getZone(); PrefabManager prefabManager = GameServer.getInstance().getPrefabManager(); - String name = String.join(" ", Arrays.copyOfRange(args, 4, args.length)).toLowerCase(); + String name = args[4].toLowerCase(); - if(prefabManager.getPrefab(name) != null) { - executor.notify("A prefab with that name already exists.", SYSTEM); + if(!overwrite && prefabManager.prefabExists(name)) { + executor.notify("A prefab with that name already exists. Add 'overwrite' at the end of the command if you wish to overwrite it.", SYSTEM); return; } else if(!PREFAB_NAME_PATTERN.matcher(name).matches()) { executor.notify("Please enter a valid prefab name. Example: dungeons/my_epic_dungeon (or just my_epic_dungeon)", SYSTEM); @@ -75,7 +75,7 @@ public void execute(CommandExecutor executor, String[] args) { executor.notify(String.format("Exporting your prefab as '%s' ...", name), SYSTEM); try { - prefabManager.addPrefab(prefab); + prefabManager.createPrefab(prefab, overwrite); executor.notify(String.format("Your prefab '%s' was successfully exported!", name), SYSTEM); } catch (Exception e) { executor.notify(String.format("An error occured while exporting prefab '%s': %s", name, e.getMessage()), SYSTEM); @@ -84,7 +84,7 @@ public void execute(CommandExecutor executor, String[] args) { @Override public String getUsage(CommandExecutor executor) { - return "/export "; + return "/export [overwrite]"; } @Override diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/FindCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/FindCommand.java similarity index 78% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/FindCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/FindCommand.java index e23e7817..966f7e1c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/FindCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/FindCommand.java @@ -1,11 +1,11 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.MetaBlock; @CommandInfo(name = "find", description = "Displays the location of a random meta block of the specified type.") diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/GenerateZoneCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/GenerateZoneCommand.java similarity index 93% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/GenerateZoneCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/GenerateZoneCommand.java index 0036466b..1de51dba 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/GenerateZoneCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/GenerateZoneCommand.java @@ -1,11 +1,11 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; import brainwine.gameserver.zone.Biome; import brainwine.gameserver.zone.Zone; import brainwine.gameserver.zone.gen.ZoneGenerator; diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/GiveCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/GiveCommand.java similarity index 90% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/GiveCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/GiveCommand.java index 9a0bd92f..fc3c619c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/GiveCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/GiveCommand.java @@ -1,17 +1,17 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import java.util.ArrayList; import java.util.List; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; +import brainwine.gameserver.player.Player; @CommandInfo(name = "give", description = "Give or take items from players.") public class GiveCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/GrowCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/GrowCommand.java similarity index 79% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/GrowCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/GrowCommand.java index 87760bde..a216aa39 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/GrowCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/GrowCommand.java @@ -1,11 +1,11 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.GrowthManager; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/HealthCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/HealthCommand.java similarity index 84% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/HealthCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/HealthCommand.java index 2e942a86..df42e037 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/HealthCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/HealthCommand.java @@ -1,12 +1,12 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; @CommandInfo(name = "health", description = "Sets the target player's health.", aliases = "hp") public class HealthCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/ImportCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/ImportCommand.java similarity index 84% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/ImportCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/ImportCommand.java index 204ae6cd..54e01d42 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/ImportCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/ImportCommand.java @@ -1,12 +1,12 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; import brainwine.gameserver.prefab.Prefab; @CommandInfo(name = "import", description = "Places a prefab at the specified location.") diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/KickCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/KickCommand.java similarity index 80% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/KickCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/KickCommand.java index 63b94ab9..417165d7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/KickCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/KickCommand.java @@ -1,14 +1,14 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import java.util.Arrays; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; @CommandInfo(name = "kick", description = "Kicks a player from the server.") public class KickCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/LevelCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/LevelCommand.java similarity index 86% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/LevelCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/LevelCommand.java index 1616b81e..1c99a483 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/LevelCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/LevelCommand.java @@ -1,12 +1,12 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; @CommandInfo(name = "level", description = "Sets the level of the target player.", aliases = { "lvl", "lv" }) public class LevelCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/MuteCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/MuteCommand.java similarity index 87% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/MuteCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/MuteCommand.java index f32b0a10..39cc651f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/MuteCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/MuteCommand.java @@ -1,16 +1,16 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.Arrays; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; import brainwine.gameserver.util.DateTimeUtils; @CommandInfo(name = "mute", description = "Mutes a player, preventing them from chatting.", aliases = "silence") diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/PlayerIdCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/PlayerIdCommand.java similarity index 78% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/PlayerIdCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/PlayerIdCommand.java index 158b1f3f..978406d7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/PlayerIdCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/PlayerIdCommand.java @@ -1,12 +1,12 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; @CommandInfo(name = "pid", description = "Displays the document id of a player.") public class PlayerIdCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/PositionCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/PositionCommand.java similarity index 68% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/PositionCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/PositionCommand.java index e05b7161..81ec1030 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/PositionCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/PositionCommand.java @@ -1,11 +1,11 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; @CommandInfo(name = "position", description = "Displays the coordinates of the block you are standing on.", aliases = { "pos", "feet", "coords", "location" }) public class PositionCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/PrefabListCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/PrefabListCommand.java similarity index 84% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/PrefabListCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/PrefabListCommand.java index dff4e216..91988d34 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/PrefabListCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/PrefabListCommand.java @@ -1,6 +1,6 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import java.util.ArrayList; import java.util.List; @@ -8,9 +8,9 @@ import org.apache.commons.lang3.math.NumberUtils; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; import brainwine.gameserver.prefab.Prefab; @CommandInfo(name = "prefabs", description = "Displays a list of all prefabs.") diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/SeedCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/SeedCommand.java similarity index 79% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/SeedCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/SeedCommand.java index e5e40a0f..1e26ac1c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/SeedCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/SeedCommand.java @@ -1,12 +1,12 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.Zone; @CommandInfo(name = "seed", description = "Displays the seed of a zone.") diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/SettleLiquidsCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/SettleLiquidsCommand.java similarity index 73% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/SettleLiquidsCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/SettleLiquidsCommand.java index fa6cf386..fd9d3873 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/SettleLiquidsCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/SettleLiquidsCommand.java @@ -1,11 +1,11 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; @CommandInfo(name = "settle", description = "Settles all liquids in all active chunks in the current zone. Warning - can cause lag!") public class SettleLiquidsCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/SkillPointsCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/SkillPointsCommand.java similarity index 86% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/SkillPointsCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/SkillPointsCommand.java index 22ccfa75..5430fe00 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/SkillPointsCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/SkillPointsCommand.java @@ -1,12 +1,12 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; @CommandInfo(name = "skillpoints", description = "Sets the skill points of the target player.", aliases = "points") public class SkillPointsCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/StopCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/StopCommand.java similarity index 74% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/StopCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/StopCommand.java index b168555a..b0a3844b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/StopCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/StopCommand.java @@ -1,9 +1,9 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; @CommandInfo(name = "stop", description = "Gracefully shuts down the server.", aliases = { "exit", "close", "shutdown" }) public class StopCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/TeleportCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/TeleportCommand.java similarity index 78% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/TeleportCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/TeleportCommand.java index d09952c2..0d1d3dac 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/TeleportCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/TeleportCommand.java @@ -1,11 +1,11 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; @CommandInfo(name = "teleport", description = "Teleports you to the specified position.", aliases = "tp") public class TeleportCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/TimeCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/TimeCommand.java similarity index 84% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/TimeCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/TimeCommand.java index 5790328c..6d949fda 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/TimeCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/TimeCommand.java @@ -1,11 +1,11 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.Zone; @CommandInfo(name = "time", description = "Displays or changes the time in the current zone.") diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/UnbanCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/UnbanCommand.java similarity index 79% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/UnbanCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/UnbanCommand.java index 934f4a2d..8e2f675c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/UnbanCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/UnbanCommand.java @@ -1,12 +1,12 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; @CommandInfo(name = "unban", description = "Unbans a player.", aliases = "pardon") public class UnbanCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/UnmuteCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/UnmuteCommand.java similarity index 78% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/UnmuteCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/UnmuteCommand.java index 23803c8f..f22e0984 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/UnmuteCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/UnmuteCommand.java @@ -1,12 +1,12 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; @CommandInfo(name = "unmute", description = "Unmutes a player.") public class UnmuteCommand extends Command { diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/WeatherCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/WeatherCommand.java similarity index 83% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/WeatherCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/WeatherCommand.java index 4b8ea6cd..618072b7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/WeatherCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/WeatherCommand.java @@ -1,11 +1,11 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.Biome; import brainwine.gameserver.zone.WeatherManager; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/commands/admin/ZoneIdCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/ZoneIdCommand.java similarity index 79% rename from gameserver/src/main/java/brainwine/gameserver/commands/admin/ZoneIdCommand.java rename to gameserver/src/main/java/brainwine/gameserver/command/admin/ZoneIdCommand.java index c674a8a6..c4346441 100644 --- a/gameserver/src/main/java/brainwine/gameserver/commands/admin/ZoneIdCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/ZoneIdCommand.java @@ -1,12 +1,12 @@ -package brainwine.gameserver.commands.admin; +package brainwine.gameserver.command.admin; -import static brainwine.gameserver.entity.player.NotificationType.SYSTEM; +import static brainwine.gameserver.player.NotificationType.SYSTEM; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.CommandInfo; -import brainwine.gameserver.commands.Command; -import brainwine.gameserver.commands.CommandExecutor; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.Zone; @CommandInfo(name = "zid", description = "Displays the document id of a zone.") diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java index 5efa62a3..becacb38 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java @@ -6,11 +6,11 @@ import java.util.List; import java.util.Map; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.Message; import brainwine.gameserver.server.messages.EffectMessage; import brainwine.gameserver.server.messages.EntityChangeMessage; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java index db53dba9..e747bde8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java @@ -17,11 +17,11 @@ import brainwine.gameserver.entity.EntityRegistry; import brainwine.gameserver.entity.FacingDirection; import brainwine.gameserver.entity.npc.behavior.SequenceBehavior; -import brainwine.gameserver.entity.player.Appearance; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; +import brainwine.gameserver.player.Appearance; +import brainwine.gameserver.player.Player; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.Vector2i; import brainwine.gameserver.util.WeightedMap; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/RandomlyTargetBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/RandomlyTargetBehavior.java index 0466adb4..3f0aab1c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/RandomlyTargetBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/RandomlyTargetBehavior.java @@ -5,7 +5,7 @@ import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.entity.npc.behavior.Behavior; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; public class RandomlyTargetBehavior extends Behavior { diff --git a/gameserver/src/main/java/brainwine/gameserver/item/Item.java b/gameserver/src/main/java/brainwine/gameserver/item/Item.java index ce497e1e..268bdb1d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/Item.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/Item.java @@ -13,7 +13,7 @@ import com.fasterxml.jackson.annotation.JsonValue; import brainwine.gameserver.dialog.DialogType; -import brainwine.gameserver.entity.player.Skill; +import brainwine.gameserver.player.Skill; import brainwine.gameserver.util.Pair; import brainwine.gameserver.util.Vector2i; import brainwine.gameserver.util.WeightedMap; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/MiningBonus.java b/gameserver/src/main/java/brainwine/gameserver/item/MiningBonus.java index ee3388bf..65d59a85 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/MiningBonus.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/MiningBonus.java @@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import brainwine.gameserver.entity.player.Skill; +import brainwine.gameserver.player.Skill; @JsonIgnoreProperties(ignoreUnknown = true) public class MiningBonus { diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/Consumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/Consumable.java index 32b3a77a..c519a301 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/consumables/Consumable.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/Consumable.java @@ -1,7 +1,7 @@ package brainwine.gameserver.item.consumables; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; public interface Consumable { diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/ConvertConsumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/ConvertConsumable.java index 8bf729ce..4681999f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/consumables/ConvertConsumable.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/ConvertConsumable.java @@ -7,10 +7,10 @@ import brainwine.gameserver.dialog.Dialog; import brainwine.gameserver.dialog.DialogSection; import brainwine.gameserver.dialog.input.DialogSelectInput; -import brainwine.gameserver.entity.player.Inventory; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; +import brainwine.gameserver.player.Inventory; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.messages.InventoryMessage; /** diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/HealConsumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/HealConsumable.java index 5473f5b1..6e0cc455 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/consumables/HealConsumable.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/HealConsumable.java @@ -1,7 +1,7 @@ package brainwine.gameserver.item.consumables; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; /** * Consumable handler for healing items diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/NameChangeConsumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/NameChangeConsumable.java index 90b3a1f0..336abbd3 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/consumables/NameChangeConsumable.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/NameChangeConsumable.java @@ -5,9 +5,9 @@ import brainwine.gameserver.GameServer; import brainwine.gameserver.dialog.Dialog; import brainwine.gameserver.dialog.DialogHelper; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.PlayerManager; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.PlayerManager; import brainwine.gameserver.server.messages.EventMessage; import brainwine.gameserver.server.messages.InventoryMessage; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/RefillConsumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/RefillConsumable.java index a965a13d..a75de86d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/consumables/RefillConsumable.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/RefillConsumable.java @@ -1,7 +1,7 @@ package brainwine.gameserver.item.consumables; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; /** * Consumable handler for steam canisters diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/SkillConsumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/SkillConsumable.java index d62a6610..60563b4a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/consumables/SkillConsumable.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/SkillConsumable.java @@ -11,9 +11,9 @@ import brainwine.gameserver.dialog.DialogHelper; import brainwine.gameserver.dialog.DialogSection; import brainwine.gameserver.dialog.input.DialogSelectInput; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.Skill; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.Skill; import brainwine.gameserver.server.messages.InventoryMessage; /** diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/SkillResetConsumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/SkillResetConsumable.java index 38205668..d9e23d47 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/consumables/SkillResetConsumable.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/SkillResetConsumable.java @@ -5,9 +5,9 @@ import brainwine.gameserver.dialog.Dialog; import brainwine.gameserver.dialog.DialogHelper; import brainwine.gameserver.dialog.DialogSection; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.Skill; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.Skill; import brainwine.gameserver.server.messages.InventoryMessage; /** diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/StealthConsumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/StealthConsumable.java index 562b49e9..aa3a0ab7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/consumables/StealthConsumable.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/StealthConsumable.java @@ -1,7 +1,7 @@ package brainwine.gameserver.item.consumables; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; /** * Consumable handler for stealth cloaks diff --git a/gameserver/src/main/java/brainwine/gameserver/item/consumables/TeleportConsumable.java b/gameserver/src/main/java/brainwine/gameserver/item/consumables/TeleportConsumable.java index fa6138e0..038b9897 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/consumables/TeleportConsumable.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/consumables/TeleportConsumable.java @@ -2,9 +2,9 @@ import java.util.List; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemUseType; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.messages.InventoryMessage; import brainwine.gameserver.zone.MetaBlock; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/BurstInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/BurstInteraction.java index a9b8573c..46eccdea 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/BurstInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/BurstInteraction.java @@ -3,11 +3,11 @@ import java.util.Map; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.Skill; import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.Skill; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.zone.Block; import brainwine.gameserver.zone.MetaBlock; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ChangeInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ChangeInteraction.java index 5b07ae8e..71736788 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ChangeInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ChangeInteraction.java @@ -1,9 +1,9 @@ package brainwine.gameserver.item.interactions; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; +import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ComposterInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ComposterInteraction.java index 781bc543..999b6412 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ComposterInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ComposterInteraction.java @@ -1,9 +1,9 @@ package brainwine.gameserver.item.interactions; -import brainwine.gameserver.entity.player.Inventory; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; +import brainwine.gameserver.player.Inventory; +import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.EcologicalMachine; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java index f7d640f2..abcd0887 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java @@ -4,13 +4,13 @@ import brainwine.gameserver.GameServer; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.player.NotificationType; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; import brainwine.gameserver.loot.Loot; +import brainwine.gameserver.player.NotificationType; +import brainwine.gameserver.player.Player; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.zone.EcologicalMachine; import brainwine.gameserver.zone.MetaBlock; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java index cd999e33..57201081 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java @@ -5,9 +5,9 @@ import java.util.Map; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; +import brainwine.gameserver.player.Player; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/EcologicalMachineInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/EcologicalMachineInteraction.java index f28f5c01..d466a253 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/EcologicalMachineInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/EcologicalMachineInteraction.java @@ -1,10 +1,10 @@ package brainwine.gameserver.item.interactions; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.player.NotificationType; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; +import brainwine.gameserver.player.NotificationType; +import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.EcologicalMachine; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ExpiatorInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ExpiatorInteraction.java index 1e8fe102..5fb928ae 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ExpiatorInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ExpiatorInteraction.java @@ -5,9 +5,9 @@ import java.util.stream.Collectors; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.player.NotificationType; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Layer; +import brainwine.gameserver.player.NotificationType; +import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.EcologicalMachine; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/GeckInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/GeckInteraction.java index 8eecb325..95107b84 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/GeckInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/GeckInteraction.java @@ -1,6 +1,6 @@ package brainwine.gameserver.item.interactions; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.EcologicalMachine; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/NoteInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/NoteInteraction.java index a58d5874..1885bc56 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/NoteInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/NoteInteraction.java @@ -7,10 +7,10 @@ import brainwine.gameserver.dialog.DialogHelper; import brainwine.gameserver.dialog.DialogSection; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.player.NotificationType; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; +import brainwine.gameserver.player.NotificationType; +import brainwine.gameserver.player.Player; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/RecyclerInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/RecyclerInteraction.java index 33cd17f2..b561b40a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/RecyclerInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/RecyclerInteraction.java @@ -7,12 +7,12 @@ import brainwine.gameserver.dialog.DialogListItem; import brainwine.gameserver.dialog.DialogSection; import brainwine.gameserver.dialog.DialogType; -import brainwine.gameserver.entity.player.Inventory; -import brainwine.gameserver.entity.player.NotificationType; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.Skill; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; +import brainwine.gameserver.player.Inventory; +import brainwine.gameserver.player.NotificationType; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.Skill; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.MathUtils; import brainwine.gameserver.zone.EcologicalMachine; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnTeleportInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnTeleportInteraction.java index f8f3c63f..3b07081e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnTeleportInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnTeleportInteraction.java @@ -2,9 +2,9 @@ import brainwine.gameserver.GameServer; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; +import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.Biome; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java index 662ce66f..3338d653 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java @@ -9,11 +9,11 @@ import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.npc.Npc; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; +import brainwine.gameserver.player.Player; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.Vector2i; import brainwine.gameserver.zone.MetaBlock; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/TargetTeleportInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/TargetTeleportInteraction.java index 01f8033d..b04bac5e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/TargetTeleportInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/TargetTeleportInteraction.java @@ -4,9 +4,9 @@ import brainwine.gameserver.dialog.Dialog; import brainwine.gameserver.dialog.DialogSection; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; +import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.Biome; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/TeleportInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/TeleportInteraction.java index 70989e27..43d8f635 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/TeleportInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/TeleportInteraction.java @@ -1,11 +1,11 @@ package brainwine.gameserver.item.interactions; import brainwine.gameserver.entity.Entity; -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.player.NotificationType; +import brainwine.gameserver.player.Player; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/TransmitInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/TransmitInteraction.java index 20bbc943..e50be386 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/TransmitInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/TransmitInteraction.java @@ -4,10 +4,10 @@ import java.util.List; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; +import brainwine.gameserver.player.Player; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/loot/LootManager.java b/gameserver/src/main/java/brainwine/gameserver/loot/LootManager.java index b1e5fba7..a55cac5c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/loot/LootManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/loot/LootManager.java @@ -2,8 +2,8 @@ import static brainwine.shared.LogMarkers.SERVER_MARKER; -import java.io.File; import java.io.IOException; +import java.net.URL; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -18,9 +18,9 @@ import com.fasterxml.jackson.core.type.TypeReference; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.Skill; -import brainwine.gameserver.util.ResourceUtils; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.Skill; +import brainwine.gameserver.resource.ResourceFinder; import brainwine.gameserver.util.WeightedMap; import brainwine.shared.JsonHelper; @@ -36,16 +36,13 @@ public LootManager() { private void loadLootTables() { logger.info(SERVER_MARKER, "Loading loot tables ..."); - File file = new File("loottables.json"); - ResourceUtils.copyDefaults("loottables.json"); - if(file.isFile()) { - try { - Map> loot = JsonHelper.readValue(file, new TypeReference>>(){}); - lootTables.putAll(loot); - } catch (IOException e) { - logger.error(SERVER_MARKER, "Failed to load loot tables", e); - } + try { + URL url = ResourceFinder.getResourceUrl("loottables.json"); + Map> loot = JsonHelper.readValue(url, new TypeReference>>(){}); + lootTables.putAll(loot); + } catch (IOException e) { + logger.error(SERVER_MARKER, "Failed to load loot tables", e); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Appearance.java b/gameserver/src/main/java/brainwine/gameserver/player/Appearance.java similarity index 97% rename from gameserver/src/main/java/brainwine/gameserver/entity/player/Appearance.java rename to gameserver/src/main/java/brainwine/gameserver/player/Appearance.java index 34684def..bf30b2b5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Appearance.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Appearance.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.entity.player; +package brainwine.gameserver.player; import java.util.ArrayList; import java.util.Collections; @@ -60,7 +60,7 @@ public static Map getRandomAppearance(Player player) { } public static List getAvailableColors(AppearanceSlot slot) { - return getAvailableColors(null); + return getAvailableColors(slot, null); } public static List getAvailableColors(AppearanceSlot slot, Player player) { diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/AppearanceSlot.java b/gameserver/src/main/java/brainwine/gameserver/player/AppearanceSlot.java similarity index 97% rename from gameserver/src/main/java/brainwine/gameserver/entity/player/AppearanceSlot.java rename to gameserver/src/main/java/brainwine/gameserver/player/AppearanceSlot.java index 43ed761a..5c15f5fe 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/AppearanceSlot.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/AppearanceSlot.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.entity.player; +package brainwine.gameserver.player; public enum AppearanceSlot { diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/ChatType.java b/gameserver/src/main/java/brainwine/gameserver/player/ChatType.java similarity index 88% rename from gameserver/src/main/java/brainwine/gameserver/entity/player/ChatType.java rename to gameserver/src/main/java/brainwine/gameserver/player/ChatType.java index d12f0ddd..cf83f5b7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/ChatType.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/ChatType.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.entity.player; +package brainwine.gameserver.player; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/ContainerType.java b/gameserver/src/main/java/brainwine/gameserver/player/ContainerType.java similarity index 88% rename from gameserver/src/main/java/brainwine/gameserver/entity/player/ContainerType.java rename to gameserver/src/main/java/brainwine/gameserver/player/ContainerType.java index 99ab9407..a9a876a5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/ContainerType.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/ContainerType.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.entity.player; +package brainwine.gameserver.player; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Inventory.java b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java similarity index 99% rename from gameserver/src/main/java/brainwine/gameserver/entity/player/Inventory.java rename to gameserver/src/main/java/brainwine/gameserver/player/Inventory.java index d4e2202c..3659e072 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Inventory.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.entity.player; +package brainwine.gameserver.player; import java.beans.ConstructorProperties; import java.util.ArrayList; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/ItemContainer.java b/gameserver/src/main/java/brainwine/gameserver/player/ItemContainer.java similarity index 96% rename from gameserver/src/main/java/brainwine/gameserver/entity/player/ItemContainer.java rename to gameserver/src/main/java/brainwine/gameserver/player/ItemContainer.java index c769d2fa..1d87192a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/ItemContainer.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/ItemContainer.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.entity.player; +package brainwine.gameserver.player; import java.util.Arrays; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/KarmaLevel.java b/gameserver/src/main/java/brainwine/gameserver/player/KarmaLevel.java similarity index 93% rename from gameserver/src/main/java/brainwine/gameserver/entity/player/KarmaLevel.java rename to gameserver/src/main/java/brainwine/gameserver/player/KarmaLevel.java index 06a3e0e2..bfc97036 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/KarmaLevel.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/KarmaLevel.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.entity.player; +package brainwine.gameserver.player; import com.fasterxml.jackson.annotation.JsonEnumDefaultValue; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/NameChange.java b/gameserver/src/main/java/brainwine/gameserver/player/NameChange.java similarity index 94% rename from gameserver/src/main/java/brainwine/gameserver/entity/player/NameChange.java rename to gameserver/src/main/java/brainwine/gameserver/player/NameChange.java index 5252cdad..9d403440 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/NameChange.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/NameChange.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.entity.player; +package brainwine.gameserver.player; import java.time.OffsetDateTime; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/NotificationType.java b/gameserver/src/main/java/brainwine/gameserver/player/NotificationType.java similarity index 93% rename from gameserver/src/main/java/brainwine/gameserver/entity/player/NotificationType.java rename to gameserver/src/main/java/brainwine/gameserver/player/NotificationType.java index a6126f1d..b372826a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/NotificationType.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/NotificationType.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.entity.player; +package brainwine.gameserver.player; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Placement.java b/gameserver/src/main/java/brainwine/gameserver/player/Placement.java similarity index 92% rename from gameserver/src/main/java/brainwine/gameserver/entity/player/Placement.java rename to gameserver/src/main/java/brainwine/gameserver/player/Placement.java index aa2411f8..7798430c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Placement.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Placement.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.entity.player; +package brainwine.gameserver.player; import brainwine.gameserver.item.Item; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java similarity index 99% rename from gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java rename to gameserver/src/main/java/brainwine/gameserver/player/Player.java index 940e22a0..e162e428 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.entity.player; +package brainwine.gameserver.player; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; @@ -19,10 +19,10 @@ import brainwine.gameserver.GameConfiguration; import brainwine.gameserver.GameServer; import brainwine.gameserver.Timer; -import brainwine.gameserver.achievements.Achievement; -import brainwine.gameserver.achievements.AchievementManager; -import brainwine.gameserver.achievements.JourneymanAchievement; -import brainwine.gameserver.commands.CommandExecutor; +import brainwine.gameserver.achievement.Achievement; +import brainwine.gameserver.achievement.AchievementManager; +import brainwine.gameserver.achievement.JourneymanAchievement; +import brainwine.gameserver.command.CommandExecutor; import brainwine.gameserver.dialog.Dialog; import brainwine.gameserver.dialog.DialogListItem; import brainwine.gameserver.dialog.DialogSection; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerConfigFile.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java similarity index 98% rename from gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerConfigFile.java rename to gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java index 03fac5d0..e91d43c1 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerConfigFile.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.entity.player; +package brainwine.gameserver.player; import java.util.ArrayList; import java.util.HashMap; @@ -12,7 +12,7 @@ import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.Nulls; -import brainwine.gameserver.achievements.Achievement; +import brainwine.gameserver.achievement.Achievement; import brainwine.gameserver.item.Item; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerManager.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java similarity index 93% rename from gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerManager.java rename to gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java index 1b4766bf..4e70c8ff 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java @@ -1,10 +1,12 @@ -package brainwine.gameserver.entity.player; +package brainwine.gameserver.player; import static brainwine.shared.LogMarkers.SERVER_MARKER; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -25,7 +27,7 @@ public class PlayerManager { private static final Logger logger = LogManager.getLogger(); private final Map playersById = new HashMap<>(); private final Map playersByName = new HashMap<>(); - private final Map playersByConnection = new HashMap<>(); + private final List onlinePlayers = new ArrayList<>(); public PlayerManager() { loadPlayers(); @@ -154,13 +156,13 @@ public void changePlayerName(Player player, String name) { public void onPlayerConnect(Player player) { Connection connection = player.getConnection(); - playersByConnection.put(connection, player); + onlinePlayers.add(player); logger.info(SERVER_MARKER, "{} logged into zone {} from {}", player.getName(), player.getZone().getName(), connection.getAddress()); } public void onPlayerDisconnect(Player player) { Connection connection = player.getConnection(); - playersByConnection.remove(connection); + onlinePlayers.remove(player); logger.info(SERVER_MARKER, "{} disconnected: {}", player.getName(), connection.getDisconnectReason()); } @@ -182,11 +184,15 @@ public Player getPlayerById(String id) { return playersById.get(id); } - public Player getPlayer(Connection connection) { - return playersByConnection.get(connection); - } - public Collection getPlayers() { return playersById.values(); } + + public int getOnlinePlayerCount() { + return onlinePlayers.size(); + } + + public List getOnlinePlayers() { + return Collections.unmodifiableList(onlinePlayers); + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerRestriction.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerRestriction.java similarity index 97% rename from gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerRestriction.java rename to gameserver/src/main/java/brainwine/gameserver/player/PlayerRestriction.java index 8e4c0544..9f96ecad 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerRestriction.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerRestriction.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.entity.player; +package brainwine.gameserver.player; import java.time.OffsetDateTime; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerStatistics.java similarity index 93% rename from gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java rename to gameserver/src/main/java/brainwine/gameserver/player/PlayerStatistics.java index 3115d335..3e548f64 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/PlayerStatistics.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerStatistics.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.entity.player; +package brainwine.gameserver.player; import java.util.Collections; import java.util.HashMap; @@ -11,19 +11,19 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import brainwine.gameserver.achievements.CraftingAchievement; -import brainwine.gameserver.achievements.DeliveranceAchievement; -import brainwine.gameserver.achievements.DiscoveryAchievement; -import brainwine.gameserver.achievements.ExploringAchievement; -import brainwine.gameserver.achievements.HuntingAchievement; -import brainwine.gameserver.achievements.LooterAchievement; -import brainwine.gameserver.achievements.MiningAchievement; -import brainwine.gameserver.achievements.RaiderAchievement; -import brainwine.gameserver.achievements.ScavengingAchievement; -import brainwine.gameserver.achievements.SidekickAchievement; -import brainwine.gameserver.achievements.SpawnerStoppageAchievement; -import brainwine.gameserver.achievements.TrappingAchievement; -import brainwine.gameserver.achievements.UndertakerAchievement; +import brainwine.gameserver.achievement.CraftingAchievement; +import brainwine.gameserver.achievement.DeliveranceAchievement; +import brainwine.gameserver.achievement.DiscoveryAchievement; +import brainwine.gameserver.achievement.ExploringAchievement; +import brainwine.gameserver.achievement.HuntingAchievement; +import brainwine.gameserver.achievement.LooterAchievement; +import brainwine.gameserver.achievement.MiningAchievement; +import brainwine.gameserver.achievement.RaiderAchievement; +import brainwine.gameserver.achievement.ScavengingAchievement; +import brainwine.gameserver.achievement.SidekickAchievement; +import brainwine.gameserver.achievement.SpawnerStoppageAchievement; +import brainwine.gameserver.achievement.TrappingAchievement; +import brainwine.gameserver.achievement.UndertakerAchievement; import brainwine.gameserver.entity.EntityConfig; import brainwine.gameserver.item.Item; diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/player/Skill.java b/gameserver/src/main/java/brainwine/gameserver/player/Skill.java similarity index 94% rename from gameserver/src/main/java/brainwine/gameserver/entity/player/Skill.java rename to gameserver/src/main/java/brainwine/gameserver/player/Skill.java index 0f2aeec4..c95e8e64 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/player/Skill.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Skill.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.entity.player; +package brainwine.gameserver.player; import com.fasterxml.jackson.annotation.JsonValue; diff --git a/gameserver/src/main/java/brainwine/gameserver/prefab/PrefabManager.java b/gameserver/src/main/java/brainwine/gameserver/prefab/PrefabManager.java index 07f18e5c..71c40739 100644 --- a/gameserver/src/main/java/brainwine/gameserver/prefab/PrefabManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/prefab/PrefabManager.java @@ -3,115 +3,105 @@ import static brainwine.shared.LogMarkers.SERVER_MARKER; import java.io.File; +import java.io.IOException; +import java.net.URL; import java.nio.file.Files; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import java.util.zip.InflaterInputStream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.msgpack.core.MessagePack; -import org.msgpack.core.MessageUnpacker; import org.msgpack.jackson.dataformat.MessagePackFactory; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.module.SimpleModule; +import brainwine.gameserver.resource.ResourceFinder; import brainwine.gameserver.serialization.BlockDeserializer; import brainwine.gameserver.serialization.BlockSerializer; -import brainwine.gameserver.util.ResourceUtils; import brainwine.gameserver.util.ZipUtils; import brainwine.gameserver.zone.Block; import brainwine.shared.JsonHelper; public class PrefabManager { + public static final String PREFAB_DIRECTORY_NAME = "prefabs"; private static final Logger logger = LogManager.getLogger(); private static final ObjectMapper mapper = JsonMapper.builder(new MessagePackFactory()) .addModule(new SimpleModule() .addDeserializer(Block.class, BlockDeserializer.INSTANCE) - .addSerializer(BlockSerializer.INSTANCE)) - .build(); - private final File dataDir = new File("prefabs"); + .addSerializer(BlockSerializer.INSTANCE)).build(); + private final File dataDirectory = new File(PREFAB_DIRECTORY_NAME); private final Map prefabs = new LinkedHashMap<>(); public PrefabManager() { + loadPrefabs(); + } + + private void loadPrefabs() { logger.info(SERVER_MARKER, "Loading prefabs ..."); - ResourceUtils.copyDefaults("prefabs/"); - if(dataDir.isDirectory()) { - for(File file : dataDir.listFiles()) { - if(file.isDirectory()) { - loadPrefab(file); - } - } - } + // Fetch prefab names and load prefabs + ResourceFinder.getResources(PREFAB_DIRECTORY_NAME).stream() + .map(x -> x.getParentDirectoryName()) + .filter(x -> x.length() > PREFAB_DIRECTORY_NAME.length()) + .map(x -> x.substring(PREFAB_DIRECTORY_NAME.length() + 1)) + .distinct() + .forEach(this::loadPrefab); - logger.info(SERVER_MARKER, "Successfully loaded {} prefab(s)", prefabs.size()); + logger.info(SERVER_MARKER, "Successfully loaded {} prefab{}", prefabs.size(), prefabs.size() == 1 ? "" : "s"); } - private void loadPrefab(File file) { - String name = file.getPath().substring(dataDir.getPath().length() + 1).replace(File.separatorChar, '/'); - File legacyBlocksFile = new File(file, "blocks.cmp"); - File configFile = new File(file, "config.json"); - File blocksFile = new File(file, "blocks.dat"); - + private void loadPrefab(String name) { try { + URL configUrl = ResourceFinder.getResourceUrl(String.format("prefabs/%s/config.json", name)); + URL blocksUrl = ResourceFinder.getResourceUrl(String.format("prefabs/%s/blocks.dat", name)); + PrefabConfigFile config = JsonHelper.readValue(configUrl, PrefabConfigFile.class); PrefabBlocksFile blockData = null; - if(legacyBlocksFile.exists() && !blocksFile.exists()) { - logger.info(SERVER_MARKER, "Updating blocks file for prefab '{}' ...", name); - MessageUnpacker unpacker = MessagePack.newDefaultUnpacker( - ZipUtils.inflateBytes(Files.readAllBytes(legacyBlocksFile.toPath()))); - int width = unpacker.unpackInt(); - int height = unpacker.unpackInt(); - Block[] blocks = new Block[unpacker.unpackArrayHeader() / 3]; - - for(int i = 0; i < blocks.length; i++) { - blocks[i] = new Block(unpacker.unpackInt(), unpacker.unpackInt(), unpacker.unpackInt()); - } - - blockData = new PrefabBlocksFile(width, height, blocks); - Files.write(blocksFile.toPath(), ZipUtils.deflateBytes(mapper.writeValueAsBytes(blockData))); - legacyBlocksFile.delete(); - } else { - // If no config or blocks file exists we will assume that this is a category directory. - // TODO maybe create a separate function for all this? - if(!configFile.exists() || !blocksFile.exists()) { - for(File childFile : file.listFiles()) { - if(childFile.isDirectory()) { - loadPrefab(childFile); - } - } - - return; - } - - blockData = mapper.readValue(ZipUtils.inflateBytes(Files.readAllBytes(blocksFile.toPath())), PrefabBlocksFile.class); + // Load block data + try(InflaterInputStream inputStream = new InflaterInputStream(blocksUrl.openStream())) { + blockData = mapper.readValue(inputStream, PrefabBlocksFile.class); } - PrefabConfigFile config = JsonHelper.readValue(configFile, PrefabConfigFile.class); + // Add prefab prefabs.put(name, new Prefab(name, config, blockData)); } catch(Exception e) { - logger.error(SERVER_MARKER, "Could not load prefab {}:", name, e); + logger.error(SERVER_MARKER, "Could not load prefab '{}'", name, e); } } - public void addPrefab(Prefab prefab) throws Exception { - String name = prefab.getName().toLowerCase(); + public boolean createPrefab(Prefab prefab) throws IOException { + return createPrefab(prefab, false); + } + + public boolean createPrefab(Prefab prefab, boolean overwrite) throws IOException { + String name = prefab.getName(); - if(prefabs.containsKey(name)) { - logger.warn(SERVER_MARKER, "Duplicate prefab name: {}", name); - return; + // Do nothing if name already exists and overwriting is not allowed + if(!overwrite && prefabExists(name)) { + return false; } - File prefabDir = new File(dataDir, name); - prefabDir.mkdirs(); - JsonHelper.writeValue(new File(prefabDir, "config.json"), new PrefabConfigFile(prefab)); - Files.write(new File(prefabDir, "blocks.dat").toPath(), ZipUtils.deflateBytes(mapper.writeValueAsBytes(new PrefabBlocksFile(prefab)))); + // Serialize & write data + byte[] configBytes = JsonHelper.writeValueAsBytes(new PrefabConfigFile(prefab)); + byte[] blockBytes = ZipUtils.deflateBytes(mapper.writeValueAsBytes(new PrefabBlocksFile(prefab))); + File outputDirectory = new File(dataDirectory, name.toLowerCase()); + outputDirectory.mkdirs(); + Files.write(new File(outputDirectory, "config.json").toPath(), configBytes); + Files.write(new File(outputDirectory, "blocks.dat").toPath(), blockBytes); + + // Index prefab prefabs.put(name, prefab); + return true; + } + + public boolean prefabExists(String name) { + return prefabs.containsKey(name); } public Prefab getPrefab(String name) { diff --git a/gameserver/src/main/java/brainwine/gameserver/resource/Resource.java b/gameserver/src/main/java/brainwine/gameserver/resource/Resource.java new file mode 100644 index 00000000..45e17f25 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/resource/Resource.java @@ -0,0 +1,34 @@ +package brainwine.gameserver.resource; + +import java.net.URL; + +public class Resource { + + private final String name; + private final String simpleName; + private final String parentDirectoryName; + private final URL url; + + public Resource(String name, String simpleName, String parentDirectoryName, URL url) { + this.name = name; + this.simpleName = simpleName; + this.parentDirectoryName = parentDirectoryName; + this.url = url; + } + + public String getName() { + return name; + } + + public String getSimpleName() { + return simpleName; + } + + public String getParentDirectoryName() { + return parentDirectoryName; + } + + public URL getUrl() { + return url; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/resource/ResourceFinder.java b/gameserver/src/main/java/brainwine/gameserver/resource/ResourceFinder.java new file mode 100644 index 00000000..1f71b372 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/resource/ResourceFinder.java @@ -0,0 +1,172 @@ +package brainwine.gameserver.resource; + +import static brainwine.shared.LogMarkers.SERVER_MARKER; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayDeque; +import java.util.HashSet; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.reflections.Reflections; +import org.reflections.scanners.Scanners; +import org.reflections.util.ClasspathHelper; +import org.reflections.util.ConfigurationBuilder; +import org.reflections.util.FilterBuilder; + +/** + * Helper class for finding the locations of gameserver configuration resources (loot tables, prefabs etc.) + */ +public class ResourceFinder { + + private static final Logger logger = LogManager.getLogger(); + + /** + * Calls {@link #getResourceUrl(String, boolean)} with {@code overridable true}. + */ + public static URL getResourceUrl(String name) { + return getResourceUrl(name, true); + } + + /** + * @param name The name of the resource. + * @param overridable If {@code true}, files in the working directory are counted and will take priority. + * @return The {@link URL} of the specified resource, or {@code null} if it doesn't exist. + */ + public static URL getResourceUrl(String name, boolean overridable) { + if(overridable) { + File file = new File(name); + + if(file.exists()) { + try { + return file.toURI().toURL(); + } catch(MalformedURLException e) { + logger.error(SERVER_MARKER, "Couldn't get URL for file '{}'", name); + return null; + } + } + } + + return ResourceFinder.class.getResource(String.format("/%s", name)); + } + + /** + * Calls {@link #getResource(String, boolean)} with {@code overridable true}. + */ + public static Resource getResource(String name) { + return getResource(name, true); + } + + /** + * @param name The name of the resource. + * @param overridable If {@code true}, files in the working directory are counted and will take priority. + * @return A {@link Resource} object for the specified resource, or {@code null} if it doesn't exist. + */ + public static Resource getResource(String name, boolean overridable) { + URL url = getResourceUrl(name, overridable); + + if(url == null) { + return null; + } + + String simpleName = name; + String parentDirectoryName = ""; // TODO null default? + int lastSeparatorIndex = name.lastIndexOf('/'); + + if(lastSeparatorIndex != -1) { + simpleName = name.substring(lastSeparatorIndex + 1); + parentDirectoryName = name.substring(0, lastSeparatorIndex); + } + + return new Resource(name, simpleName, parentDirectoryName, url); + } + + /** + * Calls {@link #getResources(String, boolean)} with {@code recursive true}. + */ + public static List getResources(String directory) { + return getResources(directory, true); + } + + /** + * Calls {@link #getResources(String, boolean, boolean)} with {@code overridable true}. + */ + public static List getResources(String directory, boolean recursive) { + return getResources(directory, recursive, true); + } + + /** + * @param directory The name of the directory containing the desired resources. + * @param recursive If {@code true}, resources in subdirectories will be counted as well. + * @param overridable If {@code true}, files in the working directory are counted and will take priority. + * @return A list of {@link #Resource} objects representing the found resources. + */ + public static List getResources(String directory, boolean recursive, boolean overridable) { + Set resourceNames = getResourceNames(directory, recursive, overridable); + return resourceNames.stream().map(x -> getResource(x, overridable)).collect(Collectors.toList()); + } + + /** + * Helper function to find resource names in a given resource directory. + */ + private static Set getResourceNames(String directory, boolean recursive, boolean overridable) { + Set resourceNames = overridable ? getFileNames(new File(directory), recursive) : new HashSet<>(); + FilterBuilder filter = new FilterBuilder().includePattern(String.format("%s/.*", directory)); + Reflections reflections = new Reflections(new ConfigurationBuilder() + .setUrls(ClasspathHelper.forResource(directory)) + .filterInputsBy(recursive ? filter : filter.excludePattern(String.format("%s/.*/.*", directory))) + .setScanners(Scanners.Resources)); + reflections.getResources(".*").stream() + .filter(x -> !resourceNames.contains(x)) + .forEach(resourceNames::add); + return resourceNames; + } + + /** + * Helper function to find file names in a given directory. + */ + private static Set getFileNames(File directory, boolean recursive) { + Set fileNames = new HashSet<>(); + Queue processQueue = new ArrayDeque<>(); + + if(directory.isDirectory()) { + for(File file : directory.listFiles()) { + processQueue.add(file); + } + } + + while(!processQueue.isEmpty()) { + File file = processQueue.poll(); + + if(file.isDirectory()) { + if(recursive) { + for(File child : file.listFiles()) { + processQueue.add(child); + } + } + } else { + String name = file.getPath().replace(File.separatorChar, '/'); // TODO will this always work and not do funny things with the path? + fileNames.add(name); + } + } + + return fileNames; + } + + /** + * @deprecated Will be moved to another location later. + */ + public static String removeFileSuffix(String string) { + if(!string.contains(".")) { + return string; + } + + return string.substring(0, string.lastIndexOf('.')); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/serialization/AchievementSerializer.java b/gameserver/src/main/java/brainwine/gameserver/serialization/AchievementSerializer.java index ca21aa80..580f6692 100644 --- a/gameserver/src/main/java/brainwine/gameserver/serialization/AchievementSerializer.java +++ b/gameserver/src/main/java/brainwine/gameserver/serialization/AchievementSerializer.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.databind.jsontype.TypeSerializer; import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import brainwine.gameserver.achievements.Achievement; +import brainwine.gameserver.achievement.Achievement; /** * Seriously, Jackson has the stupidest problems sometimes. diff --git a/gameserver/src/main/java/brainwine/gameserver/serialization/MessageSerializer.java b/gameserver/src/main/java/brainwine/gameserver/serialization/MessageSerializer.java index 98d78a86..5f5b0316 100644 --- a/gameserver/src/main/java/brainwine/gameserver/serialization/MessageSerializer.java +++ b/gameserver/src/main/java/brainwine/gameserver/serialization/MessageSerializer.java @@ -11,8 +11,8 @@ import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; public class MessageSerializer extends StdSerializer { diff --git a/gameserver/src/main/java/brainwine/gameserver/serialization/RequestDeserializer.java b/gameserver/src/main/java/brainwine/gameserver/serialization/RequestDeserializer.java index cac7d6af..622a9594 100644 --- a/gameserver/src/main/java/brainwine/gameserver/serialization/RequestDeserializer.java +++ b/gameserver/src/main/java/brainwine/gameserver/serialization/RequestDeserializer.java @@ -9,7 +9,7 @@ import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import brainwine.gameserver.annotations.OptionalField; +import brainwine.gameserver.server.OptionalField; import brainwine.gameserver.server.Request; public class RequestDeserializer extends StdDeserializer { diff --git a/gameserver/src/main/java/brainwine/gameserver/annotations/MessageInfo.java b/gameserver/src/main/java/brainwine/gameserver/server/MessageInfo.java similarity index 97% rename from gameserver/src/main/java/brainwine/gameserver/annotations/MessageInfo.java rename to gameserver/src/main/java/brainwine/gameserver/server/MessageInfo.java index 0c130b17..246d4367 100644 --- a/gameserver/src/main/java/brainwine/gameserver/annotations/MessageInfo.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/MessageInfo.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.annotations; +package brainwine.gameserver.server; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/NetworkRegistry.java b/gameserver/src/main/java/brainwine/gameserver/server/NetworkRegistry.java index d9996828..60bc67bb 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/NetworkRegistry.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/NetworkRegistry.java @@ -10,9 +10,6 @@ import org.apache.logging.log4j.Logger; import org.reflections.Reflections; -import brainwine.gameserver.annotations.MessageInfo; -import brainwine.gameserver.annotations.RequestInfo; - @SuppressWarnings("unchecked") public class NetworkRegistry { diff --git a/gameserver/src/main/java/brainwine/gameserver/annotations/OptionalField.java b/gameserver/src/main/java/brainwine/gameserver/server/OptionalField.java similarity index 86% rename from gameserver/src/main/java/brainwine/gameserver/annotations/OptionalField.java rename to gameserver/src/main/java/brainwine/gameserver/server/OptionalField.java index e7e7ff57..6faa8aeb 100644 --- a/gameserver/src/main/java/brainwine/gameserver/annotations/OptionalField.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/OptionalField.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.annotations; +package brainwine.gameserver.server; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/PlayerRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/PlayerRequest.java index 1f018ae1..648c60cf 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/PlayerRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/PlayerRequest.java @@ -1,7 +1,6 @@ package brainwine.gameserver.server; -import brainwine.gameserver.GameServer; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.pipeline.Connection; /** @@ -12,7 +11,7 @@ public abstract class PlayerRequest extends Request { public abstract void process(Player player); public final void process(Connection connection) { - Player player = GameServer.getInstance().getPlayerManager().getPlayer(connection); + Player player = connection.getPlayer(); if(player == null) { connection.kick("No player instance found."); diff --git a/gameserver/src/main/java/brainwine/gameserver/annotations/RequestInfo.java b/gameserver/src/main/java/brainwine/gameserver/server/RequestInfo.java similarity index 87% rename from gameserver/src/main/java/brainwine/gameserver/annotations/RequestInfo.java rename to gameserver/src/main/java/brainwine/gameserver/server/RequestInfo.java index 0c4bb726..0298a7d8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/annotations/RequestInfo.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/RequestInfo.java @@ -1,4 +1,4 @@ -package brainwine.gameserver.annotations; +package brainwine.gameserver.server; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/AchievementMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/AchievementMessage.java index c440f4c3..f4cd1c63 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/AchievementMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/AchievementMessage.java @@ -1,7 +1,7 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 29, collection = true) public class AchievementMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/AchievementProgressMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/AchievementProgressMessage.java index 15e1156f..263f3cf4 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/AchievementProgressMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/AchievementProgressMessage.java @@ -1,7 +1,7 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 48, collection = true) public class AchievementProgressMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/BlockChangeMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/BlockChangeMessage.java index ab63a963..1897c8d9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/BlockChangeMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/BlockChangeMessage.java @@ -3,10 +3,10 @@ import java.util.Arrays; import java.util.Collection; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; import brainwine.gameserver.server.models.BlockChangeData; @MessageInfo(id = 9, prepacked = true) 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 e90282ae..99f8a7f3 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/BlockMetaMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/BlockMetaMessage.java @@ -3,8 +3,8 @@ import java.util.Arrays; import java.util.Collection; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; import brainwine.gameserver.zone.MetaBlock; @MessageInfo(id = 20, prepacked = true) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/BlocksMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/BlocksMessage.java index 94875312..38f7b4fb 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/BlocksMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/BlocksMessage.java @@ -2,8 +2,8 @@ import java.util.Collection; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; import brainwine.gameserver.zone.Chunk; @MessageInfo(id = 3, compressed = true, prepacked = true) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/ChatMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/ChatMessage.java index 471419a3..4e22f750 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/ChatMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/ChatMessage.java @@ -1,8 +1,8 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; -import brainwine.gameserver.entity.player.ChatType; +import brainwine.gameserver.player.ChatType; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 13, collection = true) public class ChatMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/ConfigurationMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/ConfigurationMessage.java index b22dd314..7605e85f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/ConfigurationMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/ConfigurationMessage.java @@ -2,8 +2,8 @@ import java.util.Map; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 2, json = true, compressed = true) public class ConfigurationMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/DialogMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/DialogMessage.java index fc91402c..edb38b14 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/DialogMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/DialogMessage.java @@ -2,9 +2,9 @@ import java.util.Map; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.dialog.Dialog; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 45, compressed = true) public class DialogMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/EffectMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/EffectMessage.java index adae9165..4848d8f6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/EffectMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/EffectMessage.java @@ -1,8 +1,8 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.entity.Entity; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 30) public class EffectMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityChangeMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityChangeMessage.java index eeb49541..2ae472e7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityChangeMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityChangeMessage.java @@ -2,8 +2,8 @@ import java.util.Map; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 8, collection = true) public class EntityChangeMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityItemUseMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityItemUseMessage.java index 35381989..cccb6f88 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityItemUseMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityItemUseMessage.java @@ -4,10 +4,10 @@ import java.util.Collection; import java.util.stream.Collectors; -import brainwine.gameserver.annotations.MessageInfo; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; import brainwine.gameserver.server.models.EntityItemUseData; @MessageInfo(id = 10, prepacked = true) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityPositionMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityPositionMessage.java index e9c6f438..cb62b1e0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityPositionMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityPositionMessage.java @@ -4,10 +4,10 @@ import java.util.Collection; import java.util.stream.Collectors; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.FacingDirection; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; import brainwine.gameserver.server.models.EntityPositionData; @MessageInfo(id = 6, prepacked = true) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityStatusMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityStatusMessage.java index cf2c420c..84556ad6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityStatusMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/EntityStatusMessage.java @@ -5,10 +5,10 @@ import java.util.Map; import java.util.stream.Collectors; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.EntityStatus; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; import brainwine.gameserver.server.models.EntityStatusData; @MessageInfo(id = 7, prepacked = true) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/EventMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/EventMessage.java index c35e1a01..d0abacba 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/EventMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/EventMessage.java @@ -1,7 +1,7 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 57) public class EventMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/HealthMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/HealthMessage.java index 4cbe192a..49cebdac 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/HealthMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/HealthMessage.java @@ -1,7 +1,7 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 18) public class HealthMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/HeartbeatMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/HeartbeatMessage.java index c24c81ba..ae385761 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/HeartbeatMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/HeartbeatMessage.java @@ -1,7 +1,7 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 143) public class HeartbeatMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/InventoryMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/InventoryMessage.java index 638da1ef..2fa801cb 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/InventoryMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/InventoryMessage.java @@ -2,9 +2,9 @@ import java.util.Map; -import brainwine.gameserver.annotations.MessageInfo; -import brainwine.gameserver.entity.player.Inventory; +import brainwine.gameserver.player.Inventory; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 4) public class InventoryMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/KickMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/KickMessage.java index 95860011..faeb515e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/KickMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/KickMessage.java @@ -1,7 +1,7 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 255) public class KickMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/LevelMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/LevelMessage.java index 4d19fe86..6ba336ba 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/LevelMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/LevelMessage.java @@ -1,7 +1,7 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 61) public class LevelMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/LightMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/LightMessage.java index 6b9d023b..0e477f92 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/LightMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/LightMessage.java @@ -1,7 +1,7 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 15, collection = true) public class LightMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/NotificationMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/NotificationMessage.java index ab514b67..46ca8ddb 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/NotificationMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/NotificationMessage.java @@ -1,8 +1,8 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; -import brainwine.gameserver.entity.player.NotificationType; +import brainwine.gameserver.player.NotificationType; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 33) public class NotificationMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/PlayerPositionMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/PlayerPositionMessage.java index 49a026f6..84defb19 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/PlayerPositionMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/PlayerPositionMessage.java @@ -1,7 +1,7 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 5) public class PlayerPositionMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/SkillMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/SkillMessage.java index a4c91fdd..e8a3ddb0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/SkillMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/SkillMessage.java @@ -1,8 +1,8 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; -import brainwine.gameserver.entity.player.Skill; +import brainwine.gameserver.player.Skill; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 35, collection = true) public class SkillMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/StatMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/StatMessage.java index 8e82b9ce..c2a27fc3 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/StatMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/StatMessage.java @@ -1,7 +1,7 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 44, collection = true) public class StatMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/TeleportMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/TeleportMessage.java index 3cc6793d..ff08df48 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/TeleportMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/TeleportMessage.java @@ -1,7 +1,7 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 50) public class TeleportMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/WardrobeMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/WardrobeMessage.java index 1398c35f..77b47034 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/WardrobeMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/WardrobeMessage.java @@ -3,9 +3,9 @@ import java.util.Arrays; import java.util.Collection; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.item.Item; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 39) public class WardrobeMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/XpMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/XpMessage.java index de7aa075..fbc80164 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/XpMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/XpMessage.java @@ -1,7 +1,7 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 60) public class XpMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneExploredMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneExploredMessage.java index 3672dc9a..e4fec542 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneExploredMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneExploredMessage.java @@ -1,7 +1,7 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 53, collection = true) public class ZoneExploredMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneSearchMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneSearchMessage.java index 2d7132f6..22c0f0d3 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneSearchMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneSearchMessage.java @@ -5,8 +5,8 @@ import java.util.Collections; import java.util.List; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; import brainwine.gameserver.zone.Zone; @MessageInfo(id = 23) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneStatusMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneStatusMessage.java index 09c3b3b8..470bd5b6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneStatusMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneStatusMessage.java @@ -1,7 +1,7 @@ package brainwine.gameserver.server.messages; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; @MessageInfo(id = 17) public class ZoneStatusMessage extends Message { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/models/EntityItemUseData.java b/gameserver/src/main/java/brainwine/gameserver/server/models/EntityItemUseData.java index 9c663da3..294fabb7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/models/EntityItemUseData.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/models/EntityItemUseData.java @@ -3,8 +3,8 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat.Shape; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; @JsonFormat(shape = Shape.ARRAY) public class EntityItemUseData { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/pipeline/Connection.java b/gameserver/src/main/java/brainwine/gameserver/server/pipeline/Connection.java index fc657bd3..6315dcc7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/pipeline/Connection.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/pipeline/Connection.java @@ -11,7 +11,7 @@ import org.apache.logging.log4j.Logger; import brainwine.gameserver.GameServer; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.Message; import brainwine.gameserver.server.Request; import brainwine.gameserver.server.messages.KickMessage; @@ -99,6 +99,10 @@ public void setPlayer(Player player) { this.player = player; } + public Player getPlayer() { + return player; + } + public boolean isV3() { return player != null && player.isV3(); } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/pipeline/MessageEncoder.java b/gameserver/src/main/java/brainwine/gameserver/server/pipeline/MessageEncoder.java index 0cd9ec9d..bda7accc 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/pipeline/MessageEncoder.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/pipeline/MessageEncoder.java @@ -7,8 +7,8 @@ import com.fasterxml.jackson.databind.ObjectWriter; -import brainwine.gameserver.annotations.MessageInfo; import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; import brainwine.gameserver.server.NetworkRegistry; import brainwine.gameserver.util.ZipUtils; import brainwine.shared.JsonHelper; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/AdminRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/AdminRequest.java index 651b8869..9c822b34 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/AdminRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/AdminRequest.java @@ -3,11 +3,11 @@ import java.util.Collection; import java.util.stream.Collectors; -import brainwine.gameserver.annotations.OptionalField; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.commands.CommandManager; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.CommandManager; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.server.OptionalField; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; @RequestInfo(id = 254) public class AdminRequest extends PlayerRequest { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/AuthenticateRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/AuthenticateRequest.java index 6278ffeb..9d6bb837 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/AuthenticateRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/AuthenticateRequest.java @@ -3,12 +3,12 @@ import java.time.format.DateTimeFormatter; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.OptionalField; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.PlayerRestriction; -import brainwine.gameserver.entity.player.PlayerManager; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.PlayerManager; +import brainwine.gameserver.player.PlayerRestriction; +import brainwine.gameserver.server.OptionalField; import brainwine.gameserver.server.Request; +import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.server.pipeline.Connection; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index 457f091e..6ae44a71 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -4,11 +4,7 @@ import java.util.List; import java.util.Map; -import brainwine.gameserver.annotations.RequestInfo; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.player.NotificationType; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.Skill; import brainwine.gameserver.item.Action; import brainwine.gameserver.item.Fieldability; import brainwine.gameserver.item.Item; @@ -16,7 +12,11 @@ import brainwine.gameserver.item.Layer; import brainwine.gameserver.item.MiningBonus; import brainwine.gameserver.item.ModType; +import brainwine.gameserver.player.NotificationType; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.Skill; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.server.messages.BlockChangeMessage; import brainwine.gameserver.server.messages.InventoryMessage; import brainwine.gameserver.util.MapHelper; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java index ae139941..c622dfc2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java @@ -2,18 +2,18 @@ import java.util.UUID; -import brainwine.gameserver.annotations.RequestInfo; import brainwine.gameserver.entity.EntityConfig; import brainwine.gameserver.entity.npc.Npc; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.Skill; import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemGroup; import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; import brainwine.gameserver.item.ModType; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.Skill; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.server.messages.BlockChangeMessage; import brainwine.gameserver.server.messages.InventoryMessage; import brainwine.gameserver.util.MathUtils; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockUseRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockUseRequest.java index c8ecdc38..4cde5329 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockUseRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockUseRequest.java @@ -2,14 +2,14 @@ import java.util.Map; -import brainwine.gameserver.annotations.OptionalField; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; import brainwine.gameserver.item.interactions.ItemInteraction; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.server.OptionalField; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.zone.Block; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlocksIgnoreRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlocksIgnoreRequest.java index 78c299ed..6cb39494 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlocksIgnoreRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlocksIgnoreRequest.java @@ -1,8 +1,8 @@ package brainwine.gameserver.server.requests; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; @RequestInfo(id = 25) public class BlocksIgnoreRequest extends PlayerRequest { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlocksRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlocksRequest.java index 3a00167b..9f20d7a1 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlocksRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlocksRequest.java @@ -3,9 +3,9 @@ import java.util.ArrayList; import java.util.List; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.server.messages.BlockMetaMessage; import brainwine.gameserver.server.messages.BlocksMessage; import brainwine.gameserver.server.messages.LightMessage; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/ChangeAppearanceRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/ChangeAppearanceRequest.java index 6c903af8..22885ed2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/ChangeAppearanceRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/ChangeAppearanceRequest.java @@ -3,14 +3,14 @@ import java.util.Map; import java.util.Map.Entry; -import brainwine.gameserver.annotations.RequestInfo; import brainwine.gameserver.dialog.DialogHelper; -import brainwine.gameserver.entity.player.Appearance; -import brainwine.gameserver.entity.player.AppearanceSlot; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; +import brainwine.gameserver.player.Appearance; +import brainwine.gameserver.player.AppearanceSlot; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.server.messages.EntityChangeMessage; import brainwine.gameserver.util.MapHelper; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/ChatRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/ChatRequest.java index 230f9322..d03e04bf 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/ChatRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/ChatRequest.java @@ -1,11 +1,11 @@ package brainwine.gameserver.server.requests; -import brainwine.gameserver.annotations.OptionalField; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.commands.CommandManager; -import brainwine.gameserver.entity.player.NotificationType; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.CommandManager; +import brainwine.gameserver.player.NotificationType; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.server.OptionalField; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; @RequestInfo(id = 13) public class ChatRequest extends PlayerRequest { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/ConsoleRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/ConsoleRequest.java index 022e2232..b1bd6a9b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/ConsoleRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/ConsoleRequest.java @@ -1,9 +1,9 @@ package brainwine.gameserver.server.requests; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.commands.CommandManager; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.command.CommandManager; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; @RequestInfo(id = 47) public class ConsoleRequest extends PlayerRequest { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/CraftRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/CraftRequest.java index b5f9454e..df734c36 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/CraftRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/CraftRequest.java @@ -3,14 +3,14 @@ import java.util.List; import java.util.stream.Collectors; -import brainwine.gameserver.annotations.OptionalField; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.entity.player.Inventory; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.Skill; import brainwine.gameserver.item.CraftingRequirement; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Inventory; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.Skill; +import brainwine.gameserver.server.OptionalField; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.util.MathUtils; import brainwine.gameserver.util.Pair; import brainwine.gameserver.zone.MetaBlock; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java index e40904c2..66ed2358 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java @@ -7,15 +7,15 @@ import org.apache.commons.text.WordUtils; -import brainwine.gameserver.annotations.OptionalField; -import brainwine.gameserver.annotations.RequestInfo; import brainwine.gameserver.dialog.Dialog; import brainwine.gameserver.dialog.DialogHelper; import brainwine.gameserver.dialog.DialogSection; import brainwine.gameserver.dialog.input.DialogSelectInput; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.Skill; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.Skill; +import brainwine.gameserver.server.OptionalField; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; @RequestInfo(id = 45) public class DialogRequest extends PlayerRequest { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/EntitiesRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/EntitiesRequest.java index 0d0c65ce..ee5cb799 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/EntitiesRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/EntitiesRequest.java @@ -3,10 +3,10 @@ import java.util.ArrayList; import java.util.List; -import brainwine.gameserver.annotations.RequestInfo; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.server.messages.EntityStatusMessage; import brainwine.gameserver.server.models.EntityStatusData; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/EventRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/EventRequest.java index 7f9e5f81..362cf7de 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/EventRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/EventRequest.java @@ -1,8 +1,8 @@ package brainwine.gameserver.server.requests; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; @RequestInfo(id = 57) public class EventRequest extends PlayerRequest { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/HealthRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/HealthRequest.java index 4929b316..1b2c2f07 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/HealthRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/HealthRequest.java @@ -1,12 +1,12 @@ package brainwine.gameserver.server.requests; -import brainwine.gameserver.annotations.OptionalField; -import brainwine.gameserver.annotations.RequestInfo; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.server.OptionalField; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; @RequestInfo(id = 18) public class HealthRequest extends PlayerRequest { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/HeartbeatRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/HeartbeatRequest.java index 9c298f19..43f80a7a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/HeartbeatRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/HeartbeatRequest.java @@ -1,8 +1,8 @@ package brainwine.gameserver.server.requests; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; @RequestInfo(id = 143) public class HeartbeatRequest extends PlayerRequest { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/HintRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/HintRequest.java index 9adea563..49866ad8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/HintRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/HintRequest.java @@ -1,8 +1,8 @@ package brainwine.gameserver.server.requests; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; @RequestInfo(id = 36) public class HintRequest extends PlayerRequest { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryMoveRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryMoveRequest.java index 4934c26a..ba285333 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryMoveRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryMoveRequest.java @@ -1,11 +1,11 @@ package brainwine.gameserver.server.requests; -import brainwine.gameserver.annotations.OptionalField; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.entity.player.ContainerType; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.ContainerType; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.server.OptionalField; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; /** * TODO This request may be sent *before* a {@link CraftRequest} is sent. diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java index a7a6b4ee..db7b1486 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java @@ -3,13 +3,13 @@ import java.util.Arrays; import java.util.Collection; -import brainwine.gameserver.annotations.OptionalField; -import brainwine.gameserver.annotations.RequestInfo; import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.npc.Npc; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.server.OptionalField; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.server.messages.EntityItemUseMessage; /** diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/MoveRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/MoveRequest.java index ed0115fc..ef1833e2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/MoveRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/MoveRequest.java @@ -1,10 +1,10 @@ package brainwine.gameserver.server.requests; -import brainwine.gameserver.annotations.RequestInfo; import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.FacingDirection; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.server.messages.EntityPositionMessage; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/RespawnRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/RespawnRequest.java index 981519eb..882cc80f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/RespawnRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/RespawnRequest.java @@ -1,8 +1,8 @@ package brainwine.gameserver.server.requests; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; @RequestInfo(id = 26) public class RespawnRequest extends PlayerRequest { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/StatusRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/StatusRequest.java index 3d925435..d3125a8f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/StatusRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/StatusRequest.java @@ -1,8 +1,8 @@ package brainwine.gameserver.server.requests; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; @RequestInfo(id = 54) public class StatusRequest extends PlayerRequest { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/TransactionRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/TransactionRequest.java index 2c5759db..15cf08f6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/TransactionRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/TransactionRequest.java @@ -1,8 +1,8 @@ package brainwine.gameserver.server.requests; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.server.messages.StatMessage; @RequestInfo(id = 41) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneChangeRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneChangeRequest.java index ccedb626..02e3af7b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneChangeRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneChangeRequest.java @@ -1,9 +1,9 @@ package brainwine.gameserver.server.requests; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.zone.Zone; @RequestInfo(id = 24) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneSearchRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneSearchRequest.java index d6212248..50ff6d01 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneSearchRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneSearchRequest.java @@ -7,9 +7,9 @@ import java.util.Set; import brainwine.gameserver.GameServer; -import brainwine.gameserver.annotations.RequestInfo; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.server.messages.ZoneSearchMessage; import brainwine.gameserver.zone.Biome; import brainwine.gameserver.zone.Zone; diff --git a/gameserver/src/main/java/brainwine/gameserver/util/ResourceUtils.java b/gameserver/src/main/java/brainwine/gameserver/util/ResourceUtils.java deleted file mode 100644 index 3c44ce30..00000000 --- a/gameserver/src/main/java/brainwine/gameserver/util/ResourceUtils.java +++ /dev/null @@ -1,83 +0,0 @@ -package brainwine.gameserver.util; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.util.Set; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.reflections.Reflections; -import org.reflections.scanners.Scanners; -import org.reflections.util.ClasspathHelper; -import org.reflections.util.ConfigurationBuilder; - -public class ResourceUtils { - - private static final Logger logger = LogManager.getLogger(); - - public static InputStream getOverridableResource(String name) throws IOException { - File file = new File(name); - - if(!file.exists()) { - return ResourceUtils.class.getResourceAsStream(String.format("/defaults/%s", name)); - } - - return new FileInputStream(file); - } - - public static Set getResourceNames(String directory) { - Reflections reflections = new Reflections(new ConfigurationBuilder() - .setUrls(ClasspathHelper.forResource("defaults")) - .setInputsFilter(x -> x.matches(String.format("defaults/%s.*", directory))) - .setScanners(Scanners.Resources)); - return reflections.getResources(".*"); - } - - @Deprecated - public static void copyDefaults(String path) { - copyDefaults(path, false); - } - - @Deprecated - public static void copyDefaults(String path, boolean force) { - try { - File file = new File(path); - - if(!file.exists() || force) { - Reflections reflections = new Reflections(new ConfigurationBuilder() - .setUrls(ClasspathHelper.forResource("defaults")) - .setInputsFilter(x -> x.matches(String.format("defaults/%s.*", path))) - .setScanners(Scanners.Resources)); - Set fileNames = reflections.getResources(".*"); - - for(String fileName : fileNames) { - File output = new File(fileName.substring(9)); - File parent = output.getAbsoluteFile().getParentFile(); - - if(parent != null) { - parent.mkdirs(); - } - - try { - Files.copy(ResourceUtils.class.getResourceAsStream(String.format("/%s", fileName)), output.toPath()); - } catch (Exception e) { - logger.error("Couldn't copy resource '{}'", fileName, e); - } - } - } - } catch(Exception e) { - logger.error("Couldn't copy defaults '{}'", path, e); - } - } - - public static String removeFileSuffix(String string) { - if(!string.contains(".")) { - return string; - } - - return string.substring(0, string.lastIndexOf('.')); - } -} diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ChunkManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/ChunkManager.java index 4417c717..27914494 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ChunkManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ChunkManager.java @@ -26,7 +26,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; -import brainwine.gameserver.entity.player.Player; +import brainwine.gameserver.player.Player; import brainwine.gameserver.serialization.BlockDeserializer; import brainwine.gameserver.serialization.BlockSerializer; import brainwine.gameserver.util.ZipUtils; diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java index 66b565d5..51b53fca 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java @@ -2,8 +2,8 @@ import static brainwine.shared.LogMarkers.SERVER_MARKER; -import java.io.File; import java.io.IOException; +import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -26,14 +26,14 @@ import brainwine.gameserver.entity.EntityStatus; import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.entity.npc.NpcData; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; import brainwine.gameserver.item.ModType; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.resource.ResourceFinder; import brainwine.gameserver.server.messages.EntityPositionMessage; import brainwine.gameserver.server.messages.EntityStatusMessage; import brainwine.gameserver.util.MapHelper; -import brainwine.gameserver.util.ResourceUtils; import brainwine.gameserver.util.Vector2i; import brainwine.gameserver.util.WeightedMap; import brainwine.shared.JsonHelper; @@ -61,15 +61,12 @@ public EntityManager(Zone zone) { public static void loadEntitySpawns() { spawns.clear(); logger.info(SERVER_MARKER, "Loading entity spawns ..."); - File file = new File("spawning.json"); - ResourceUtils.copyDefaults("spawning.json"); - if(file.isFile()) { - try { - spawns.putAll(JsonHelper.readValue(file, new TypeReference>>(){})); - } catch (IOException e) { - logger.error(SERVER_MARKER, "Failed to load entity spawns", e); - } + try { + URL url = ResourceFinder.getResourceUrl("spawning.json", true); + spawns.putAll(JsonHelper.readValue(url, new TypeReference>>(){})); + } catch (IOException e) { + logger.error(SERVER_MARKER, "Failed to load entity spawns", e); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/GrowthManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/GrowthManager.java index 9333ebf8..1d4af2f2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/GrowthManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/GrowthManager.java @@ -2,7 +2,7 @@ import static brainwine.shared.LogMarkers.SERVER_MARKER; -import java.io.InputStream; +import java.net.URL; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -18,7 +18,7 @@ import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; -import brainwine.gameserver.util.ResourceUtils; +import brainwine.gameserver.resource.ResourceFinder; import brainwine.gameserver.util.WeightedMap; import brainwine.shared.JsonHelper; @@ -40,17 +40,15 @@ public GrowthManager(Zone zone) { this.zone = zone; } - /** - * Loads growth data configuration - */ public static void loadGrowthData() { growables.clear(); sourcesByBiome.clear(); - try(InputStream inputStream = ResourceUtils.getOverridableResource("growth.json")) { - Map data = JsonHelper.readValue(inputStream, new TypeReference>(){}); + try { + URL url = ResourceFinder.getResourceUrl("growth.json"); + Map data = JsonHelper.readValue(url, new TypeReference>(){}); growables.putAll(JsonHelper.readValue(data.get("growables"), new TypeReference>(){})); - sourcesByBiome.putAll(JsonHelper.readValue(data.get("sources"), new TypeReference>>>(){})); + sourcesByBiome.putAll(JsonHelper.readValue(data.get("sources"), new TypeReference>>>(){})); } catch(Exception e) { logger.error(SERVER_MARKER, "Could not load growth data", e); } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/MachineManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/MachineManager.java index a86ecf0d..bd36f436 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/MachineManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/MachineManager.java @@ -10,8 +10,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; import brainwine.gameserver.server.messages.ZoneStatusMessage; import brainwine.gameserver.util.MapHelper; diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java b/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java index b4f53e7a..6a27331c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java @@ -12,8 +12,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; import brainwine.gameserver.GameServer; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.Player; /** * I hate this class and everything in it. diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index c7c35bea..bb7c1de5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -26,9 +26,6 @@ import brainwine.gameserver.entity.Entity; import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.entity.npc.NpcData; -import brainwine.gameserver.entity.player.ChatType; -import brainwine.gameserver.entity.player.NotificationType; -import brainwine.gameserver.entity.player.Player; import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Fieldability; import brainwine.gameserver.item.Item; @@ -37,6 +34,9 @@ import brainwine.gameserver.item.Layer; import brainwine.gameserver.item.MetaType; import brainwine.gameserver.item.ModType; +import brainwine.gameserver.player.ChatType; +import brainwine.gameserver.player.NotificationType; +import brainwine.gameserver.player.Player; import brainwine.gameserver.prefab.Prefab; import brainwine.gameserver.server.Message; import brainwine.gameserver.server.messages.BlockChangeMessage; @@ -74,6 +74,7 @@ public class Zone { private int[] sunlight; private int[] depths; private boolean[] chunksExplored; + private int chunksExploredCount; private OffsetDateTime creationDate = OffsetDateTime.now(); private float time = (float)Math.random(); // TODO temporary private float temperature; @@ -106,6 +107,7 @@ protected Zone(String documentId, ZoneConfigFile config, ZoneDataFile data) { this.sunlight = sunlight != null && sunlight.length == width ? sunlight : this.sunlight; this.depths = depths != null && depths.length == 3 ? depths : this.depths; this.chunksExplored = chunksExplored != null && chunksExplored.length == getChunkCount() ? chunksExplored : this.chunksExplored; + recalculateChunksExploredCount(); steamManager.setData(data.getSteamData()); machineManager.loadData(config); pendingSunlight.addAll(data.getPendingSunlight()); @@ -1525,6 +1527,7 @@ public boolean exploreArea(int x, int y, Player explorer) { explorer.getStatistics().trackAreaExplored(); } + chunksExploredCount++; sendMessage(new ZoneExploredMessage(chunkIndex)); return chunksExplored[chunkIndex] = true; } @@ -1553,15 +1556,17 @@ public boolean[] getChunksExplored() { } public int getChunksExploredCount() { - int count = 0; + return chunksExploredCount; + } + + private void recalculateChunksExploredCount() { + chunksExploredCount = 0; for(boolean explored : chunksExplored) { if(explored) { - count++; + chunksExploredCount++; } } - - return count; } @JsonValue diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java index 61666851..3265883b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java @@ -16,17 +16,15 @@ import java.util.Map; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.zip.DataFormatException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.msgpack.core.MessagePack; -import org.msgpack.core.MessageUnpacker; import org.msgpack.jackson.dataformat.MessagePackFactory; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import brainwine.gameserver.GameServer; import brainwine.gameserver.entity.npc.NpcData; import brainwine.gameserver.util.ZipUtils; import brainwine.gameserver.zone.gen.ZoneGenerator; @@ -88,7 +86,7 @@ public void tick(float deltaTime) { final long PLAYER_COUNT_INFLUENCE = 16; if (!generatingZone && timeSinceLastGeneration > MIN_GENERATION_INTERVAL_SECONDS) { - int playerCount = zones.values().stream().map(Zone::getPlayerCount).reduce(Integer::sum).orElse(0); + int playerCount = GameServer.getInstance().getPlayerManager().getOnlinePlayerCount(); long requiredInterval = Math.max( MIN_GENERATION_INTERVAL_SECONDS, GENERATION_INTERVAL_ZERO_PLAYERS_SECONDS - (playerCount - 1) * (GENERATION_INTERVAL_ZERO_PLAYERS_SECONDS - MIN_GENERATION_INTERVAL_SECONDS) / PLAYER_COUNT_INFLUENCE @@ -128,16 +126,12 @@ private void loadZone(File file) { File metaBlocksFile = new File(file, "metablocks.json"); File charactersFile = new File(file, "characters.json"); - try { - ZoneDataFile data = null; - + try { if(legacyDataFile.exists() && !dataFile.exists()) { - data = convertLegacyDataFile(legacyDataFile, dataFile); - // legacyDataFile.delete(); Let's just keep it.. - } else { - data = mapper.readValue(ZipUtils.inflateBytes(Files.readAllBytes(dataFile.toPath())), ZoneDataFile.class); + throw new IOException("Zone data format is outdated. Please try to load this zone with an older server version to update it."); } + ZoneDataFile data = mapper.readValue(ZipUtils.inflateBytes(Files.readAllBytes(dataFile.toPath())), ZoneDataFile.class); ZoneConfigFile config = JsonHelper.readValue(configFile, ZoneConfigFile.class); Zone zone = new Zone(id, config, data); @@ -158,38 +152,6 @@ private void loadZone(File file) { } } - private ZoneDataFile convertLegacyDataFile(File legacyFile, File outputFile) throws IOException, DataFormatException { - MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(ZipUtils.inflateBytes(Files.readAllBytes(legacyFile.toPath()))); - int[] surface = new int[unpacker.unpackArrayHeader()]; - - for(int i = 0; i < surface.length; i++) { - surface[i] = unpacker.unpackInt(); - } - - int[] sunlight = new int[unpacker.unpackArrayHeader()]; - - for(int i = 0; i < sunlight.length; i++) { - sunlight[i] = unpacker.unpackInt(); - } - - List pendingSunlight = new ArrayList<>(); - int pendingSunlightSize = unpacker.unpackArrayHeader(); - - for(int i = 0; i < pendingSunlightSize; i++) { - pendingSunlight.add(unpacker.unpackInt()); - } - - boolean[] chunksExplored = new boolean[unpacker.unpackArrayHeader()]; - - for(int i = 0; i < chunksExplored.length; i++) { - chunksExplored[i] = unpacker.unpackBoolean(); - } - - ZoneDataFile data = new ZoneDataFile(surface, sunlight, null, pendingSunlight, chunksExplored, null); - Files.write(outputFile.toPath(), ZipUtils.deflateBytes(mapper.writeValueAsBytes(data))); - return data; - } - public void saveZones() { for(Zone zone : getZones()) { saveZone(zone); diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java index 77a2573f..17e55cb9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java @@ -2,7 +2,6 @@ import static brainwine.shared.LogMarkers.SERVER_MARKER; -import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.Random; @@ -15,7 +14,8 @@ import brainwine.gameserver.GameServer; import brainwine.gameserver.Naming; import brainwine.gameserver.item.Layer; -import brainwine.gameserver.util.ResourceUtils; +import brainwine.gameserver.resource.Resource; +import brainwine.gameserver.resource.ResourceFinder; import brainwine.gameserver.zone.Biome; import brainwine.gameserver.zone.Zone; import brainwine.gameserver.zone.gen.tasks.CaveGeneratorTask; @@ -50,24 +50,20 @@ public ZoneGenerator(GeneratorConfig config) { public static void init() { generators.clear(); logger.info(SERVER_MARKER, "Loading zone generator configurations ..."); - ResourceUtils.copyDefaults("generators/"); - File dataDir = new File("generators"); - if(dataDir.isDirectory()) { - for(File file : dataDir.listFiles()) { - try { - String name = ResourceUtils.removeFileSuffix(file.getName()).toLowerCase(); - - if(generators.containsKey(name)) { - logger.warn(SERVER_MARKER, "Duplicate generator config name '{}'", name); - continue; - } - - GeneratorConfig config = JsonHelper.readValue(file, GeneratorConfig.class); - generators.put(name, new ZoneGenerator(config)); - } catch(Exception e) { - logger.error(SERVER_MARKER, "Failed to load generator config '{}'", file.getName(), e); - } + for(Resource resource : ResourceFinder.getResources("generators", false)) { + String name = ResourceFinder.removeFileSuffix(resource.getSimpleName()).toLowerCase(); + + if(generators.containsKey(name)) { + logger.warn(SERVER_MARKER, "Duplicate generator config name '{}'", name); + continue; + } + + try { + GeneratorConfig config = JsonHelper.readValue(resource.getUrl(), GeneratorConfig.class); + generators.put(name, new ZoneGenerator(config)); + } catch(Exception e) { + logger.error(SERVER_MARKER, "Failed to load generator config '{}'", name, e); } } diff --git a/gameserver/src/main/resources/defaults/generators/arctic.json b/gameserver/src/main/resources/generators/arctic.json similarity index 100% rename from gameserver/src/main/resources/defaults/generators/arctic.json rename to gameserver/src/main/resources/generators/arctic.json diff --git a/gameserver/src/main/resources/defaults/generators/brain.json b/gameserver/src/main/resources/generators/brain.json similarity index 100% rename from gameserver/src/main/resources/defaults/generators/brain.json rename to gameserver/src/main/resources/generators/brain.json diff --git a/gameserver/src/main/resources/defaults/generators/deep.json b/gameserver/src/main/resources/generators/deep.json similarity index 100% rename from gameserver/src/main/resources/defaults/generators/deep.json rename to gameserver/src/main/resources/generators/deep.json diff --git a/gameserver/src/main/resources/defaults/generators/desert.json b/gameserver/src/main/resources/generators/desert.json similarity index 100% rename from gameserver/src/main/resources/defaults/generators/desert.json rename to gameserver/src/main/resources/generators/desert.json diff --git a/gameserver/src/main/resources/defaults/generators/hell.json b/gameserver/src/main/resources/generators/hell.json similarity index 100% rename from gameserver/src/main/resources/defaults/generators/hell.json rename to gameserver/src/main/resources/generators/hell.json diff --git a/gameserver/src/main/resources/defaults/generators/plain.json b/gameserver/src/main/resources/generators/plain.json similarity index 100% rename from gameserver/src/main/resources/defaults/generators/plain.json rename to gameserver/src/main/resources/generators/plain.json diff --git a/gameserver/src/main/resources/defaults/generators/space.json b/gameserver/src/main/resources/generators/space.json similarity index 100% rename from gameserver/src/main/resources/defaults/generators/space.json rename to gameserver/src/main/resources/generators/space.json diff --git a/gameserver/src/main/resources/defaults/growth.json b/gameserver/src/main/resources/growth.json similarity index 100% rename from gameserver/src/main/resources/defaults/growth.json rename to gameserver/src/main/resources/growth.json diff --git a/gameserver/src/main/resources/defaults/loottables.json b/gameserver/src/main/resources/loottables.json similarity index 100% rename from gameserver/src/main/resources/defaults/loottables.json rename to gameserver/src/main/resources/loottables.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/arctic_surface_1/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/arctic_surface_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/arctic_surface_1/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/arctic_surface_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/arctic_surface_1/config.json b/gameserver/src/main/resources/prefabs/dungeons/arctic_surface_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/arctic_surface_1/config.json rename to gameserver/src/main/resources/prefabs/dungeons/arctic_surface_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_1/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_large_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_1/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_large_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_1/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_large_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_1/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_large_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_2/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_large_2/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_2/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_large_2/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_2/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_large_2/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_2/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_large_2/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_3/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_large_3/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_3/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_large_3/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_3/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_large_3/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_3/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_large_3/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_4/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_large_4/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_4/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_large_4/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_4/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_large_4/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_4/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_large_4/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_5/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_large_5/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_5/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_large_5/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_5/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_large_5/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_5/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_large_5/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_6/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_large_6/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_6/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_large_6/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_6/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_large_6/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_large_6/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_large_6/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_1/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_1/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_1/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_1/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_10/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_10/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_10/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_10/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_10/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_10/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_10/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_10/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_11/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_11/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_11/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_11/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_11/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_11/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_11/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_11/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_2/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_2/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_2/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_2/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_2/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_2/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_2/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_2/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_3/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_3/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_3/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_3/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_3/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_3/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_3/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_3/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_4/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_4/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_4/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_4/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_4/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_4/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_4/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_4/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_5/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_5/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_5/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_5/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_5/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_5/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_5/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_5/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_6/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_6/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_6/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_6/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_6/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_6/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_6/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_6/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_7/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_7/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_7/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_7/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_7/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_7/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_7/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_7/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_8/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_8/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_8/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_8/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_8/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_8/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_8/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_8/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_9/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_9/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_9/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_9/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_9/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_medium_9/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_medium_9/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_medium_9/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_1/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_small_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_1/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_1/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_small_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_1/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_2/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_small_2/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_2/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_2/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_2/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_small_2/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_2/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_2/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_3/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_small_3/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_3/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_3/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_3/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_small_3/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_3/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_3/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_4/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_small_4/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_4/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_4/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_4/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_small_4/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_4/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_4/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_5/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_small_5/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_5/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_5/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_5/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_small_5/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_5/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_5/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_6/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_small_6/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_6/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_6/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_6/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_small_6/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_6/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_6/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_7/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_small_7/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_7/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_7/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_7/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_small_7/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_7/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_7/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_8/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_small_8/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_8/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_8/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_8/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_small_8/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_8/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_8/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_9/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/generic_small_9/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_9/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_9/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_9/config.json b/gameserver/src/main/resources/prefabs/dungeons/generic_small_9/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/generic_small_9/config.json rename to gameserver/src/main/resources/prefabs/dungeons/generic_small_9/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/hell_small_1/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/hell_small_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/hell_small_1/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/hell_small_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/hell_small_1/config.json b/gameserver/src/main/resources/prefabs/dungeons/hell_small_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/hell_small_1/config.json rename to gameserver/src/main/resources/prefabs/dungeons/hell_small_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/hell_surface_1/blocks.dat b/gameserver/src/main/resources/prefabs/dungeons/hell_surface_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/hell_surface_1/blocks.dat rename to gameserver/src/main/resources/prefabs/dungeons/hell_surface_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/dungeons/hell_surface_1/config.json b/gameserver/src/main/resources/prefabs/dungeons/hell_surface_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/dungeons/hell_surface_1/config.json rename to gameserver/src/main/resources/prefabs/dungeons/hell_surface_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/bucket_shrine/blocks.dat b/gameserver/src/main/resources/prefabs/misc/bucket_shrine/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/bucket_shrine/blocks.dat rename to gameserver/src/main/resources/prefabs/misc/bucket_shrine/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/bucket_shrine/config.json b/gameserver/src/main/resources/prefabs/misc/bucket_shrine/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/bucket_shrine/config.json rename to gameserver/src/main/resources/prefabs/misc/bucket_shrine/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/gift_bunker_brass/blocks.dat b/gameserver/src/main/resources/prefabs/misc/gift_bunker_brass/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/gift_bunker_brass/blocks.dat rename to gameserver/src/main/resources/prefabs/misc/gift_bunker_brass/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/gift_bunker_brass/config.json b/gameserver/src/main/resources/prefabs/misc/gift_bunker_brass/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/gift_bunker_brass/config.json rename to gameserver/src/main/resources/prefabs/misc/gift_bunker_brass/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/gift_bunker_iron/blocks.dat b/gameserver/src/main/resources/prefabs/misc/gift_bunker_iron/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/gift_bunker_iron/blocks.dat rename to gameserver/src/main/resources/prefabs/misc/gift_bunker_iron/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/gift_bunker_iron/config.json b/gameserver/src/main/resources/prefabs/misc/gift_bunker_iron/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/gift_bunker_iron/config.json rename to gameserver/src/main/resources/prefabs/misc/gift_bunker_iron/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/gift_bunker_wood/blocks.dat b/gameserver/src/main/resources/prefabs/misc/gift_bunker_wood/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/gift_bunker_wood/blocks.dat rename to gameserver/src/main/resources/prefabs/misc/gift_bunker_wood/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/gift_bunker_wood/config.json b/gameserver/src/main/resources/prefabs/misc/gift_bunker_wood/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/gift_bunker_wood/config.json rename to gameserver/src/main/resources/prefabs/misc/gift_bunker_wood/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/head_bunker/blocks.dat b/gameserver/src/main/resources/prefabs/misc/head_bunker/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/head_bunker/blocks.dat rename to gameserver/src/main/resources/prefabs/misc/head_bunker/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/head_bunker/config.json b/gameserver/src/main/resources/prefabs/misc/head_bunker/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/head_bunker/config.json rename to gameserver/src/main/resources/prefabs/misc/head_bunker/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/luxury_bunker/blocks.dat b/gameserver/src/main/resources/prefabs/misc/luxury_bunker/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/luxury_bunker/blocks.dat rename to gameserver/src/main/resources/prefabs/misc/luxury_bunker/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/luxury_bunker/config.json b/gameserver/src/main/resources/prefabs/misc/luxury_bunker/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/luxury_bunker/config.json rename to gameserver/src/main/resources/prefabs/misc/luxury_bunker/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/machine_bunker/blocks.dat b/gameserver/src/main/resources/prefabs/misc/machine_bunker/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/machine_bunker/blocks.dat rename to gameserver/src/main/resources/prefabs/misc/machine_bunker/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/machine_bunker/config.json b/gameserver/src/main/resources/prefabs/misc/machine_bunker/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/machine_bunker/config.json rename to gameserver/src/main/resources/prefabs/misc/machine_bunker/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/music_bunker/blocks.dat b/gameserver/src/main/resources/prefabs/misc/music_bunker/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/music_bunker/blocks.dat rename to gameserver/src/main/resources/prefabs/misc/music_bunker/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/music_bunker/config.json b/gameserver/src/main/resources/prefabs/misc/music_bunker/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/music_bunker/config.json rename to gameserver/src/main/resources/prefabs/misc/music_bunker/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/painting_bunker/blocks.dat b/gameserver/src/main/resources/prefabs/misc/painting_bunker/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/painting_bunker/blocks.dat rename to gameserver/src/main/resources/prefabs/misc/painting_bunker/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/painting_bunker/config.json b/gameserver/src/main/resources/prefabs/misc/painting_bunker/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/painting_bunker/config.json rename to gameserver/src/main/resources/prefabs/misc/painting_bunker/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/sculpture_bunker_double/blocks.dat b/gameserver/src/main/resources/prefabs/misc/sculpture_bunker_double/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/sculpture_bunker_double/blocks.dat rename to gameserver/src/main/resources/prefabs/misc/sculpture_bunker_double/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/sculpture_bunker_double/config.json b/gameserver/src/main/resources/prefabs/misc/sculpture_bunker_double/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/sculpture_bunker_double/config.json rename to gameserver/src/main/resources/prefabs/misc/sculpture_bunker_double/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/sculpture_bunker_single/blocks.dat b/gameserver/src/main/resources/prefabs/misc/sculpture_bunker_single/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/sculpture_bunker_single/blocks.dat rename to gameserver/src/main/resources/prefabs/misc/sculpture_bunker_single/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/misc/sculpture_bunker_single/config.json b/gameserver/src/main/resources/prefabs/misc/sculpture_bunker_single/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/misc/sculpture_bunker_single/config.json rename to gameserver/src/main/resources/prefabs/misc/sculpture_bunker_single/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_1/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_1/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_1/config.json b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_1/config.json rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_2/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_2/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_2/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_2/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_2/config.json b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_2/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_2/config.json rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_2/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_3/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_3/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_3/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_3/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_3/config.json b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_3/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_3/config.json rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_3/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_4/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_4/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_4/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_4/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_4/config.json b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_4/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_4/config.json rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_4/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_5/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_5/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_5/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_5/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_5/config.json b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_5/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_5/config.json rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_5/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_6/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_6/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_6/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_6/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_6/config.json b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_6/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_6/config.json rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_6/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_7/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_7/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_7/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_7/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_7/config.json b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_7/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_7/config.json rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_7/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_8/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_8/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_8/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_8/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_8/config.json b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_8/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_8/config.json rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_8/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_9/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_9/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_9/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_9/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_9/config.json b/gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_9/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/arctic_surface_building_9/config.json rename to gameserver/src/main/resources/prefabs/ruins/arctic_surface_building_9/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/desert_surface_building_1/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/desert_surface_building_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/desert_surface_building_1/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/desert_surface_building_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/desert_surface_building_1/config.json b/gameserver/src/main/resources/prefabs/ruins/desert_surface_building_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/desert_surface_building_1/config.json rename to gameserver/src/main/resources/prefabs/ruins/desert_surface_building_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/desert_surface_building_2/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/desert_surface_building_2/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/desert_surface_building_2/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/desert_surface_building_2/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/desert_surface_building_2/config.json b/gameserver/src/main/resources/prefabs/ruins/desert_surface_building_2/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/desert_surface_building_2/config.json rename to gameserver/src/main/resources/prefabs/ruins/desert_surface_building_2/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/desert_surface_building_3/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/desert_surface_building_3/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/desert_surface_building_3/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/desert_surface_building_3/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/desert_surface_building_3/config.json b/gameserver/src/main/resources/prefabs/ruins/desert_surface_building_3/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/desert_surface_building_3/config.json rename to gameserver/src/main/resources/prefabs/ruins/desert_surface_building_3/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/desert_surface_building_4/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/desert_surface_building_4/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/desert_surface_building_4/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/desert_surface_building_4/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/desert_surface_building_4/config.json b/gameserver/src/main/resources/prefabs/ruins/desert_surface_building_4/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/desert_surface_building_4/config.json rename to gameserver/src/main/resources/prefabs/ruins/desert_surface_building_4/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_buried_house_1/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/generic_buried_house_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_buried_house_1/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/generic_buried_house_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_buried_house_1/config.json b/gameserver/src/main/resources/prefabs/ruins/generic_buried_house_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_buried_house_1/config.json rename to gameserver/src/main/resources/prefabs/ruins/generic_buried_house_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_buried_house_2/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/generic_buried_house_2/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_buried_house_2/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/generic_buried_house_2/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_buried_house_2/config.json b/gameserver/src/main/resources/prefabs/ruins/generic_buried_house_2/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_buried_house_2/config.json rename to gameserver/src/main/resources/prefabs/ruins/generic_buried_house_2/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_buried_house_3/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/generic_buried_house_3/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_buried_house_3/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/generic_buried_house_3/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_buried_house_3/config.json b/gameserver/src/main/resources/prefabs/ruins/generic_buried_house_3/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_buried_house_3/config.json rename to gameserver/src/main/resources/prefabs/ruins/generic_buried_house_3/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_metal_1/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/generic_metal_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_metal_1/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/generic_metal_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_metal_1/config.json b/gameserver/src/main/resources/prefabs/ruins/generic_metal_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_metal_1/config.json rename to gameserver/src/main/resources/prefabs/ruins/generic_metal_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_metal_2/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/generic_metal_2/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_metal_2/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/generic_metal_2/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_metal_2/config.json b/gameserver/src/main/resources/prefabs/ruins/generic_metal_2/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_metal_2/config.json rename to gameserver/src/main/resources/prefabs/ruins/generic_metal_2/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_metal_3/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/generic_metal_3/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_metal_3/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/generic_metal_3/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_metal_3/config.json b/gameserver/src/main/resources/prefabs/ruins/generic_metal_3/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_metal_3/config.json rename to gameserver/src/main/resources/prefabs/ruins/generic_metal_3/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_metal_4/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/generic_metal_4/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_metal_4/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/generic_metal_4/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_metal_4/config.json b/gameserver/src/main/resources/prefabs/ruins/generic_metal_4/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_metal_4/config.json rename to gameserver/src/main/resources/prefabs/ruins/generic_metal_4/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_stone_1/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/generic_stone_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_stone_1/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/generic_stone_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_stone_1/config.json b/gameserver/src/main/resources/prefabs/ruins/generic_stone_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_stone_1/config.json rename to gameserver/src/main/resources/prefabs/ruins/generic_stone_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_1/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/generic_wood_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_1/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/generic_wood_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_1/config.json b/gameserver/src/main/resources/prefabs/ruins/generic_wood_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_1/config.json rename to gameserver/src/main/resources/prefabs/ruins/generic_wood_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_2/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/generic_wood_2/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_2/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/generic_wood_2/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_2/config.json b/gameserver/src/main/resources/prefabs/ruins/generic_wood_2/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_2/config.json rename to gameserver/src/main/resources/prefabs/ruins/generic_wood_2/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_3/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/generic_wood_3/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_3/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/generic_wood_3/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_3/config.json b/gameserver/src/main/resources/prefabs/ruins/generic_wood_3/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_3/config.json rename to gameserver/src/main/resources/prefabs/ruins/generic_wood_3/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_4/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/generic_wood_4/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_4/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/generic_wood_4/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_4/config.json b/gameserver/src/main/resources/prefabs/ruins/generic_wood_4/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_4/config.json rename to gameserver/src/main/resources/prefabs/ruins/generic_wood_4/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_5/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/generic_wood_5/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_5/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/generic_wood_5/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_5/config.json b/gameserver/src/main/resources/prefabs/ruins/generic_wood_5/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/generic_wood_5/config.json rename to gameserver/src/main/resources/prefabs/ruins/generic_wood_5/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_1/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/hell_surface_building_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_1/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/hell_surface_building_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_1/config.json b/gameserver/src/main/resources/prefabs/ruins/hell_surface_building_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_1/config.json rename to gameserver/src/main/resources/prefabs/ruins/hell_surface_building_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_2/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/hell_surface_building_2/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_2/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/hell_surface_building_2/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_2/config.json b/gameserver/src/main/resources/prefabs/ruins/hell_surface_building_2/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_2/config.json rename to gameserver/src/main/resources/prefabs/ruins/hell_surface_building_2/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_3/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/hell_surface_building_3/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_3/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/hell_surface_building_3/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_3/config.json b/gameserver/src/main/resources/prefabs/ruins/hell_surface_building_3/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_3/config.json rename to gameserver/src/main/resources/prefabs/ruins/hell_surface_building_3/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_4/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/hell_surface_building_4/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_4/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/hell_surface_building_4/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_4/config.json b/gameserver/src/main/resources/prefabs/ruins/hell_surface_building_4/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_4/config.json rename to gameserver/src/main/resources/prefabs/ruins/hell_surface_building_4/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_5/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/hell_surface_building_5/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_5/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/hell_surface_building_5/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_5/config.json b/gameserver/src/main/resources/prefabs/ruins/hell_surface_building_5/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_5/config.json rename to gameserver/src/main/resources/prefabs/ruins/hell_surface_building_5/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_6/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/hell_surface_building_6/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_6/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/hell_surface_building_6/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_6/config.json b/gameserver/src/main/resources/prefabs/ruins/hell_surface_building_6/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_6/config.json rename to gameserver/src/main/resources/prefabs/ruins/hell_surface_building_6/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_7/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/hell_surface_building_7/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_7/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/hell_surface_building_7/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_7/config.json b/gameserver/src/main/resources/prefabs/ruins/hell_surface_building_7/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/hell_surface_building_7/config.json rename to gameserver/src/main/resources/prefabs/ruins/hell_surface_building_7/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_1/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_1/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_1/config.json b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_1/config.json rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_10/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_10/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_10/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_10/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_10/config.json b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_10/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_10/config.json rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_10/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_2/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_2/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_2/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_2/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_2/config.json b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_2/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_2/config.json rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_2/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_3/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_3/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_3/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_3/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_3/config.json b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_3/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_3/config.json rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_3/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_4/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_4/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_4/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_4/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_4/config.json b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_4/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_4/config.json rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_4/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_5/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_5/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_5/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_5/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_5/config.json b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_5/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_5/config.json rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_5/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_6/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_6/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_6/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_6/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_6/config.json b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_6/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_6/config.json rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_6/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_7/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_7/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_7/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_7/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_7/config.json b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_7/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_7/config.json rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_7/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_8/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_8/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_8/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_8/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_8/config.json b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_8/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_8/config.json rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_8/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_9/blocks.dat b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_9/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_9/blocks.dat rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_9/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_9/config.json b/gameserver/src/main/resources/prefabs/ruins/plain_surface_building_9/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/ruins/plain_surface_building_9/config.json rename to gameserver/src/main/resources/prefabs/ruins/plain_surface_building_9/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/arctic_1/blocks.dat b/gameserver/src/main/resources/prefabs/spawns/arctic_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/arctic_1/blocks.dat rename to gameserver/src/main/resources/prefabs/spawns/arctic_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/arctic_1/config.json b/gameserver/src/main/resources/prefabs/spawns/arctic_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/arctic_1/config.json rename to gameserver/src/main/resources/prefabs/spawns/arctic_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/arctic_2/blocks.dat b/gameserver/src/main/resources/prefabs/spawns/arctic_2/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/arctic_2/blocks.dat rename to gameserver/src/main/resources/prefabs/spawns/arctic_2/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/arctic_2/config.json b/gameserver/src/main/resources/prefabs/spawns/arctic_2/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/arctic_2/config.json rename to gameserver/src/main/resources/prefabs/spawns/arctic_2/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/arctic_3/blocks.dat b/gameserver/src/main/resources/prefabs/spawns/arctic_3/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/arctic_3/blocks.dat rename to gameserver/src/main/resources/prefabs/spawns/arctic_3/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/arctic_3/config.json b/gameserver/src/main/resources/prefabs/spawns/arctic_3/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/arctic_3/config.json rename to gameserver/src/main/resources/prefabs/spawns/arctic_3/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/brain_1/blocks.dat b/gameserver/src/main/resources/prefabs/spawns/brain_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/brain_1/blocks.dat rename to gameserver/src/main/resources/prefabs/spawns/brain_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/brain_1/config.json b/gameserver/src/main/resources/prefabs/spawns/brain_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/brain_1/config.json rename to gameserver/src/main/resources/prefabs/spawns/brain_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/deep_1/blocks.dat b/gameserver/src/main/resources/prefabs/spawns/deep_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/deep_1/blocks.dat rename to gameserver/src/main/resources/prefabs/spawns/deep_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/deep_1/config.json b/gameserver/src/main/resources/prefabs/spawns/deep_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/deep_1/config.json rename to gameserver/src/main/resources/prefabs/spawns/deep_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/desert_1/blocks.dat b/gameserver/src/main/resources/prefabs/spawns/desert_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/desert_1/blocks.dat rename to gameserver/src/main/resources/prefabs/spawns/desert_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/desert_1/config.json b/gameserver/src/main/resources/prefabs/spawns/desert_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/desert_1/config.json rename to gameserver/src/main/resources/prefabs/spawns/desert_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/desert_2/blocks.dat b/gameserver/src/main/resources/prefabs/spawns/desert_2/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/desert_2/blocks.dat rename to gameserver/src/main/resources/prefabs/spawns/desert_2/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/desert_2/config.json b/gameserver/src/main/resources/prefabs/spawns/desert_2/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/desert_2/config.json rename to gameserver/src/main/resources/prefabs/spawns/desert_2/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/hell_1/blocks.dat b/gameserver/src/main/resources/prefabs/spawns/hell_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/hell_1/blocks.dat rename to gameserver/src/main/resources/prefabs/spawns/hell_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/hell_1/config.json b/gameserver/src/main/resources/prefabs/spawns/hell_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/hell_1/config.json rename to gameserver/src/main/resources/prefabs/spawns/hell_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/hell_2/blocks.dat b/gameserver/src/main/resources/prefabs/spawns/hell_2/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/hell_2/blocks.dat rename to gameserver/src/main/resources/prefabs/spawns/hell_2/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/hell_2/config.json b/gameserver/src/main/resources/prefabs/spawns/hell_2/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/hell_2/config.json rename to gameserver/src/main/resources/prefabs/spawns/hell_2/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/hell_3/blocks.dat b/gameserver/src/main/resources/prefabs/spawns/hell_3/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/hell_3/blocks.dat rename to gameserver/src/main/resources/prefabs/spawns/hell_3/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/hell_3/config.json b/gameserver/src/main/resources/prefabs/spawns/hell_3/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/hell_3/config.json rename to gameserver/src/main/resources/prefabs/spawns/hell_3/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/plain_1/blocks.dat b/gameserver/src/main/resources/prefabs/spawns/plain_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/plain_1/blocks.dat rename to gameserver/src/main/resources/prefabs/spawns/plain_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/plain_1/config.json b/gameserver/src/main/resources/prefabs/spawns/plain_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/plain_1/config.json rename to gameserver/src/main/resources/prefabs/spawns/plain_1/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/plain_2/blocks.dat b/gameserver/src/main/resources/prefabs/spawns/plain_2/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/plain_2/blocks.dat rename to gameserver/src/main/resources/prefabs/spawns/plain_2/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/plain_2/config.json b/gameserver/src/main/resources/prefabs/spawns/plain_2/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/plain_2/config.json rename to gameserver/src/main/resources/prefabs/spawns/plain_2/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/plain_3/blocks.dat b/gameserver/src/main/resources/prefabs/spawns/plain_3/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/plain_3/blocks.dat rename to gameserver/src/main/resources/prefabs/spawns/plain_3/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/plain_3/config.json b/gameserver/src/main/resources/prefabs/spawns/plain_3/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/plain_3/config.json rename to gameserver/src/main/resources/prefabs/spawns/plain_3/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/plain_4/blocks.dat b/gameserver/src/main/resources/prefabs/spawns/plain_4/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/plain_4/blocks.dat rename to gameserver/src/main/resources/prefabs/spawns/plain_4/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/plain_4/config.json b/gameserver/src/main/resources/prefabs/spawns/plain_4/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/plain_4/config.json rename to gameserver/src/main/resources/prefabs/spawns/plain_4/config.json diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/space_1/blocks.dat b/gameserver/src/main/resources/prefabs/spawns/space_1/blocks.dat similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/space_1/blocks.dat rename to gameserver/src/main/resources/prefabs/spawns/space_1/blocks.dat diff --git a/gameserver/src/main/resources/defaults/prefabs/spawns/space_1/config.json b/gameserver/src/main/resources/prefabs/spawns/space_1/config.json similarity index 100% rename from gameserver/src/main/resources/defaults/prefabs/spawns/space_1/config.json rename to gameserver/src/main/resources/prefabs/spawns/space_1/config.json diff --git a/gameserver/src/main/resources/defaults/spawning.json b/gameserver/src/main/resources/spawning.json similarity index 100% rename from gameserver/src/main/resources/defaults/spawning.json rename to gameserver/src/main/resources/spawning.json diff --git a/shared/src/main/java/brainwine/shared/JsonHelper.java b/shared/src/main/java/brainwine/shared/JsonHelper.java index d25d5778..334c08ea 100644 --- a/shared/src/main/java/brainwine/shared/JsonHelper.java +++ b/shared/src/main/java/brainwine/shared/JsonHelper.java @@ -3,6 +3,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import java.util.List; import com.fasterxml.jackson.core.JsonGenerationException; @@ -42,6 +43,10 @@ public static T readValue(File file, Class type) throws IOException { return MAPPER.readValue(file, type); } + public static T readValue(URL url, Class type) throws IOException { + return MAPPER.readValue(url, type); + } + public static T readValue(InputStream inputStream, Class type) throws IOException { return MAPPER.readValue(inputStream, type); } @@ -58,6 +63,10 @@ public static T readValue(File file, TypeReference type) throws IOExcepti return MAPPER.readValue(file, type); } + public static T readValue(URL url, TypeReference type) throws IOException { + return MAPPER.readValue(url, type); + } + public static T readValue(InputStream inputStream, TypeReference type) throws IOException { return MAPPER.readValue(inputStream, type); } diff --git a/src/main/java/brainwine/DirectDataFetcher.java b/src/main/java/brainwine/DirectDataFetcher.java index ad6fdaa8..3a6b6f16 100644 --- a/src/main/java/brainwine/DirectDataFetcher.java +++ b/src/main/java/brainwine/DirectDataFetcher.java @@ -6,8 +6,8 @@ import brainwine.api.DataFetcher; import brainwine.api.models.ZoneInfo; -import brainwine.gameserver.entity.player.Player; -import brainwine.gameserver.entity.player.PlayerManager; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.PlayerManager; import brainwine.gameserver.zone.Zone; import brainwine.gameserver.zone.ZoneManager; diff --git a/src/main/java/brainwine/ServerThread.java b/src/main/java/brainwine/ServerThread.java index 47994ed5..3b7279b0 100644 --- a/src/main/java/brainwine/ServerThread.java +++ b/src/main/java/brainwine/ServerThread.java @@ -8,7 +8,7 @@ import brainwine.api.Api; import brainwine.gameserver.GameServer; import brainwine.gameserver.TickLoop; -import brainwine.gameserver.commands.CommandManager; +import brainwine.gameserver.command.CommandManager; public class ServerThread extends Thread { From a0d9b4097aab5e80be1201f11ad19f440db32a6a Mon Sep 17 00:00:00 2001 From: Kuroppoi Date: Wed, 22 May 2024 19:42:57 +0200 Subject: [PATCH 084/176] Add multi-item trading (#52) --- .../brainwine/gameserver/player/Player.java | 43 ++ .../gameserver/player/TradeSession.java | 639 ++++++++++++++++++ .../server/requests/EntityUseRequest.java | 44 ++ 3 files changed, 726 insertions(+) create mode 100644 gameserver/src/main/java/brainwine/gameserver/player/TradeSession.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/server/requests/EntityUseRequest.java diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index e162e428..bed73341 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -110,6 +110,7 @@ public class Player extends Entity implements CommandExecutor { private final List> timers = new ArrayList<>(); private final List trackedEntities = new ArrayList<>(); private String clientVersion; + private TradeSession tradeSession; private Placement lastPlacement; private Item heldItem = Item.AIR; private int spawnX; @@ -193,6 +194,11 @@ public void tick(float deltaTime) { heal(BASE_REGEN_AMOUNT * deltaTime); } + // Try to timeout trade + if(isTrading()) { + tradeSession.timeout(); + } + // Process timers timers.removeIf(Timer::process); @@ -396,6 +402,11 @@ public void onDisconnect() { nextZone = null; } + // Cancel existing trade session + if(isTrading()) { + tradeSession.cancel(this); + } + dialogs.clear(); activeChunks.clear(); @@ -639,6 +650,38 @@ public Item getHeldItem() { return heldItem; } + public void tradeItem(Player recipient, Item item) { + // Cannot trade with self + if(recipient == this) { + return; + } + + // Cancel the current trade if the player is initiating a new trade + if(isTrading() && !tradeSession.isParticipant(recipient)) { + tradeSession.cancel(this); + } + + // Create a new trade session if it doesn't exist + if(!isTrading()) { + tradeSession = new TradeSession(this, recipient); + } + + // Process the offer + tradeSession.onItemOffered(this, item); + } + + public void setTradeSession(TradeSession tradeSession) { + this.tradeSession = tradeSession; + } + + public boolean isTrading() { + return tradeSession != null; + } + + public TradeSession getTradeSession() { + return tradeSession; + } + public void trackPlacement(int x, int y, Item item) { if(item.getUses().isEmpty() || !zone.areCoordinatesInBounds(x, y)) { return; diff --git a/gameserver/src/main/java/brainwine/gameserver/player/TradeSession.java b/gameserver/src/main/java/brainwine/gameserver/player/TradeSession.java new file mode 100644 index 00000000..575461d0 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/player/TradeSession.java @@ -0,0 +1,639 @@ +package brainwine.gameserver.player; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import brainwine.gameserver.dialog.Dialog; +import brainwine.gameserver.dialog.DialogHelper; +import brainwine.gameserver.dialog.DialogListItem; +import brainwine.gameserver.dialog.DialogSection; +import brainwine.gameserver.dialog.input.DialogSelectInput; +import brainwine.gameserver.item.Item; + +/** + * Manages a trade session between two players: an initiator and a recipient. + * + * The initiator is the one initiating the trade, and is the first to make an offer. + * The recipient is the player whom the initiator makes an offer to. + * + * The recipient will functionally not be aware of the trade until the initiator submits their offer, + * and will not be part of this trade (by holding the {@code TradeSession} object) until accepting it. + * + * After submitting their offer, the initiator should not be allowed to make any new offers + * to the recipient while the trade is active, and initiating a trade with a different player + * should result in a cancellation of the current trade first. + * + * When the recipient accepts the initiator's trade request, it is their turn to make a counter-offer. + * This works the same as before, but the "Give freely" option should be excluded from the dialog. + * Once the counter-offer is submitted, the recipient should be blocked from making new offers + * just like how it was with the initiator. + * + * If the initiator accepts the recipient's counter-offer, one final inventory check should be done + * and if both parties have the required items, the trade is finalized. + * + * TODO Find a good way to track trades & implement achievements. + */ +public class TradeSession { + + /** + * Trade states. + */ + private enum State { + /** + * The initiator is currently making an offer. + * The recipient is not aware of the trade at this point. + */ + INITIATOR_OFFERING, + + /** + * The initiator has submitted their offer and is being viewed by the recipient. + */ + RECIPIENT_VIEWING_OFFER, + + /** + * The recipient has accepted the initiator's trade request and is currently making a counter-offer. + */ + RECIPIENT_OFFERING, + + /** + * The recipient has submitted their counter-offer and is being viewed by the initiator. + * If the initiator accepts the offer, the trade will be finalized. + */ + INITIATOR_VIEWING_OFFER, + + /** + * The trade has ended and is no longer valid. + */ + TRADE_ENDED + } + + public static final int ITEM_LIMIT = 8; // Maximum number of different items + private final Map initiatorOffers = new HashMap<>(); + private final Map recipientOffers = new HashMap<>(); + private final Player initiator; + private final Player recipient; + private boolean isRecipientAware; // Whether or not the recipient is aware of this trade + private State state = State.INITIATOR_OFFERING; + private long timeoutAt; + + public TradeSession(Player initiator, Player recipient) { + this.initiator = initiator; + this.recipient = recipient; + } + + /** + * Called when one of the participating players drags an item to offer. + */ + protected void onItemOffered(Player player, Item item) { + // Do nothing if state is invalid + if((player == initiator && state != State.INITIATOR_OFFERING) || (player == recipient && state != State.RECIPIENT_OFFERING)) { + return; + } + + Map offers = getOffers(player); + + // Do nothing if item limit has been reached and item is not already part of the offer + if(offers.size() >= ITEM_LIMIT && !offers.containsKey(item)) { + player.notify("You cannot offer any more items."); + return; + } + + Player otherPlayer = getOtherPlayer(player); + player.showDialog(Dialogs.createQuantitySelectorDialog(player, otherPlayer, item), input -> { + // Handle cancellation + if(input.length == 0 || (input.length == 1 && input[0].equals("cancel"))) { + cancel(player); + return; + } + + // Parse quantity + int quantity = 0; + + try { + quantity = Integer.parseInt(String.valueOf(input[0])); + } catch(NumberFormatException e) { + abort(); + return; + } + + // Process input + onItemQuantitySelected(player, item, quantity); + }); + + // Update timeout + setTimeoutSeconds(10); + } + + /** + * Called by {@link #onItemOffered(Player, Item)} when the player submits an item quantity. + */ + private void onItemQuantitySelected(Player player, Item item, int quantity) { + // Do nothing if state is invalid + if((player == initiator && state != State.INITIATOR_OFFERING) || (player == recipient && state != State.RECIPIENT_OFFERING)) { + return; + } + + // Abort if quantiy is invalid or if the player does not have enough of this item + if(quantity <= 0 || !player.getInventory().hasItem(item, quantity)) { + abort(); + return; + } + + Map offers = getOffers(player); + + // Do nothing if item limit has been reached and item is not already part of the offer + if(offers.size() >= ITEM_LIMIT && !offers.containsKey(item)) { + return; + } + + // Store offer + offers.put(item, quantity); + + // Show offer status dialog + if(player == initiator) { + player.showDialog(Dialogs.createInitiatorOfferStatusDialog(recipient, offers), input -> { + // Validate input + if(input.length != 1) { + abort(); + return; + } + + // Handle action + switch(String.valueOf(input[0])) { + case "Request trade": + onInitiatorSendTradeRequest(); + break; + case "Give freely": + onInitiatorGiveFreely(); + break; + case "cancel": + // Quirk: V3 auto-cancels dialogs when a new one pops up, which isn't very helpful here... + if(!player.isV3()) { + cancel(player); + } + + break; + default: + abort(); + break; + } + }); + } else if(player == recipient) { + player.showDialog(Dialogs.createRecipientOfferStatusDialog(initiator, initiatorOffers, offers), input -> { + // Validate input + if(input.length != 1) { + abort(); + return; + } + + // Handle action + switch(String.valueOf(input[0])) { + case "Submit offer": + onRecipientSubmitOffer(); + break; + case "Cancel": + // Yes, the uppercase 'C' is important. + cancel(player); + break; + } + }); + } + + // Update timeout + setTimeoutSeconds(20); + } + + /** + * Called by {@link #onItemQuantitySelected(Player, Item, int)} when the initiator chooses to give their offer freely. + * TODO Should there be a confirmation dialog? + */ + private void onInitiatorGiveFreely() { + // Do nothing if state is invalid + if(state != State.INITIATOR_OFFERING) { + return; + } + + // End trade if recipient is currently unavailable + if(!canTrade(recipient)) { + initiator.showDialog(DialogHelper.messageDialog(String.format("%s cannot receive items right now -- try again in a minute.", recipient.getName()))); + end(); + return; + } + + // Check initiator's inventory + if(!checkInventory(initiator)) { + abort("The trade could not be fulfilled."); + return; + } + + // Try to end the trade + if(!end()) { + return; + } + + // Deliver goodies + initiatorOffers.forEach((item, quantity) -> { + initiator.getInventory().removeItem(item, quantity, true); + recipient.getInventory().addItem(item, quantity, true); + }); + + // Show feedback + initiator.showDialog(Dialogs.createOfferDialog(String.format("You sent free goodies to %s!", recipient.getName()), "Sent:", initiatorOffers)); + recipient.showDialog(Dialogs.createOfferDialog(String.format("You received goodies from %s!", initiator.getName()), "Received:", initiatorOffers)); + } + + /** + * Called by {@link #onItemQuantitySelected(Player, Item, int)} when the initiator submits their offer. + */ + private void onInitiatorSendTradeRequest() { + // Do nothing if state is invalid + if(state != State.INITIATOR_OFFERING) { + return; + } + + // Abort trade if no offers are present + if(initiatorOffers.isEmpty()) { + abort(); + return; + } + + // End trade if recipient is unavailable + if(!canTrade(recipient)) { + initiator.showDialog(DialogHelper.messageDialog(String.format("%s cannot trade right now -- try again in a minute.", recipient.getName()))); + end(); + return; + } + + // Update trade state + state = State.RECIPIENT_VIEWING_OFFER; + isRecipientAware = true; + + // Show feedback to initiator + initiator.showDialog(Dialogs.createOfferDialog("Your offer has been sent:", initiatorOffers)); + + // Show trade request dialog to recipient + recipient.showDialog(Dialogs.createOfferDialog(String.format("%s wants to trade:", initiator.getName()), null, "Are you interested?", initiatorOffers).setActions("yesno"), input -> { + // Handle cancellation + if(input.length == 1 && input[0].equals("cancel")) { + cancel(recipient); + return; + } + + onRecipientAcceptTradeRequest(); + }); + + // Update timeout + setTimeoutSeconds(10); + } + + /** + * Called by {@link #onInitiatorTradeRequest()} when the recipient accepts the initiator's trade request. + */ + private void onRecipientAcceptTradeRequest() { + // Do nothing if state is invalid + if(state != State.RECIPIENT_VIEWING_OFFER) { + return; + } + + // Cancel recipient's existing trade session if it exists + if(recipient.isTrading()) { + recipient.getTradeSession().cancel(recipient); + } + + // Update trade state + state = State.RECIPIENT_OFFERING; + recipient.setTradeSession(this); + + // Show feedback to initiator + initiator.showDialog(DialogHelper.messageDialog(String.format("%s accepted your trade request.", recipient.getName()), "Their offer will be along shortly.")); + + // Show feedback to recipient + recipient.showDialog(Dialogs.createOfferDialog("You accepted a trade request for:", null, + String.format("Drag the item you'd like to trade to %s, then select the amount to offer.", initiator.getName()), initiatorOffers)); + + // Update timeout + setTimeoutSeconds(20); + } + + /** + * Called by {@link #onItemQuantitySelected(Player, Item, int)} when the recipient submits their counter-offer. + */ + private void onRecipientSubmitOffer() { + // Do nothing if state is invalid + if(state != State.RECIPIENT_OFFERING) { + return; + } + + // Abort trade if no offers are present + if(recipientOffers.isEmpty()) { + abort(); + return; + } + + // Update state + state = State.INITIATOR_VIEWING_OFFER; + + // Show feedback to recipient + recipient.showDialog(Dialogs.createOfferDialog("Your offer has been sent:", recipientOffers)); + + // Show the recipient's offer to the initiator + initiator.showDialog(Dialogs.createFinalOfferDialog(initiator, recipient, initiatorOffers, recipientOffers), input -> { + // Handle cancellation + if(input.length == 1 && input[0].equals("cancel")) { + cancel(initiator); + return; + } + + // Finalize the trade + complete(); + }); + + // Update timeout + setTimeoutSeconds(20); + } + + /** + * Called by {@link #onRecipientSubmitOffer()} when the initiator accepts the recipient's counter-offer. + * Checks both parties' inventories and completes the trade if everything checks out. + */ + private void complete() { + // Check inventory of both players + if(!checkInventory(initiator) || !checkInventory(recipient)) { + abort("The trade could not be fulfilled."); + return; + } + + // Try to end the trade + if(!end()) { + return; + } + + // Update inventories + initiatorOffers.forEach((item, quantity) -> { + initiator.getInventory().removeItem(item, quantity, true); + recipient.getInventory().addItem(item, quantity, true); + }); + + recipientOffers.forEach((item, quantity) -> { + recipient.getInventory().removeItem(item, quantity, true); + initiator.getInventory().addItem(item, quantity, true); + }); + + // Show trade completion dialog + initiator.showDialog(Dialogs.createOfferDialog(String.format("You traded with %s.", recipient.getName()), "Received:", recipientOffers)); + recipient.showDialog(Dialogs.createOfferDialog(String.format("You traded with %s.", initiator.getName()), "Received:", initiatorOffers)); + } + + /** + * @param player The player whose inventory to be check. + * @return {@code true} if the player has enough of each item they have offered, otherwise {@code false}. + */ + private boolean checkInventory(Player player) { + Map offers = getOffers(player); + + for(Entry entry : offers.entrySet()) { + if(!player.getInventory().hasItem(entry.getKey(), entry.getValue())) { + return false; + } + } + + return true; + } + + /** + * Calls {@link #abort(String)} with a generic message. + */ + private void abort() { + abort("The trade was cancelled due to an error."); + } + + /** + * Aborts the trade and notifies the participants with the specified message. + * The recipient will not be notified of the cancellation if they aren't aware of the trade. + * + * @param message The message to display. Can be {@code null}, in which case no message will be shown. + */ + private void abort(String message) { + // Do nothing if the trade has already ended + if(!end()) { + return; + } + + // Don't notify if message isn't present + if(message == null) { + return; + } + + initiator.showDialog(DialogHelper.messageDialog(message)); + + // Only notify recipient if they are aware of this trade + if(isRecipientAware) { + recipient.showDialog(DialogHelper.messageDialog(message)); + } + } + + /** + * Cancels the trade and notifies the participants. + * The recipient will not be notified of the cancellation if they aren't aware of the trade. + * + * @param canceller The player who cancelled the trade. + */ + public void cancel(Player canceller) { + // Do nothing if the trade has already ended + if(!end()) { + return; + } + + String message = String.format("%s cancelled the trade.", canceller.getName()); + String cancellerMessage = String.format("You cancelled the trade with %s.", getOtherPlayer(canceller).getName()); + initiator.showDialog(DialogHelper.messageDialog(initiator == canceller ? cancellerMessage : message)); + + // Only notify recipient if they are aware of this trade + if(isRecipientAware) { + recipient.showDialog(DialogHelper.messageDialog(recipient == canceller ? cancellerMessage : message)); + } + } + + /** + * Ends the trade if it has timed out due to an action taking too long. + */ + public void timeout() { + // Do nothing if it's not time yet + if(System.currentTimeMillis() < timeoutAt) { + return; + } + + // Try to end the trade + if(!end()) { + return; + } + + initiator.showDialog(DialogHelper.messageDialog(String.format("Your trade with %s has timed out.", recipient.getName()))); + + // Only notify recipient if they are aware of this trade + if(isRecipientAware) { + recipient.showDialog(DialogHelper.messageDialog(String.format("Your trade with %s has timed out.", initiator.getName()))); + } + } + + /** + * Sets the state to {@link State#TRADE_ENDED} and sets the trade session of both players to {@code null} + * if they are in this trade according to {@link #isTradeCurrent(Player)} + * + * @return {@code false} if the trade has already been ended, otherwise {@code true}. + */ + private boolean end() { + if(state == State.TRADE_ENDED) { + return false; + } + + if(isTradeCurrent(initiator)) initiator.setTradeSession(null); + if(isTradeCurrent(recipient)) recipient.setTradeSession(null); + state = State.TRADE_ENDED; + return true; + } + + /** + * Updates the timeout value. + * + * @param seconds The amount of seconds until the trade should timeout. + */ + private void setTimeoutSeconds(int seconds) { + timeoutAt = System.currentTimeMillis() + seconds * 1000L; + } + + /** + * @return The current offers of the given player, or {@code null} if the player is not part of this trade. + */ + private Map getOffers(Player player) { + return player == initiator ? initiatorOffers : player == recipient ? recipientOffers : null; + } + + /** + * @return The other trade participant that is not the specified one, or {@code null} if the player is not part of this trade. + */ + private Player getOtherPlayer(Player player) { + return player == initiator ? recipient : player == recipient ? initiator : null; + } + + /** + * @return {@code true} if the given player is either the initiator or recipient of this trade, otherwise {@code false}. + */ + public boolean isParticipant(Player player) { + return player == initiator || player == recipient; + } + + /** + * @return {@code true} if the given player is available to trade right now, otherwise {@code false}. + */ + public boolean canTrade(Player player) { + return recipient.isOnline() && recipient.getZone() == initiator.getZone() && !recipient.isTrading(); + } + + /** + * @return {@code true} if the given player is currently in this trade, otherwise {@code false}. + * + * A player is considered to be part of the trade when they are either: + * - The initiator of the trade + * - The recipient and have accepted the initiator's request to trade + */ + private boolean isTradeCurrent(Player player) { + return player.getTradeSession() == this; + } + + /** + * Helper class for creating trading-related dialogs. + */ + private static class Dialogs { + + public static final List ITEM_QUANTITY_OPTIONS = + Arrays.asList("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "15", "20", "25", "30", "40", "50", "75", "100", "200", "500", "1000", "5000", "25000", "100000"); + + public static Dialog createQuantitySelectorDialog(Player offerer, Player target, Item item) { + // Get quantity options that are available to the player + List quantityOptions = ITEM_QUANTITY_OPTIONS.stream() + .filter(quantity -> offerer.getInventory().hasItem(item, Integer.parseInt(quantity))) + .collect(Collectors.toList()); + + return new Dialog() + .addSection(new DialogSection() + .setTitle(String.format("Trade with %s", target.getName()))) + .addSection(new DialogSection() + .setText(String.format("Quantity of %s to trade:", item.getTitle())) + .setInput(new DialogSelectInput() + .setOptions(quantityOptions) + .setKey("quantity"))); + } + + public static Dialog createInitiatorOfferStatusDialog(Player recipient, Map offers) { + Dialog dialog = createOfferDialog("Your current offer:", offers); + + // Add multi-item trading hint if the item limit hasn't been reached yet + if(offers.size() < TradeSession.ITEM_LIMIT) { + dialog.addSection(new DialogSection() + .setText(String.format("Drag another item to %s to include it in your offer. " + + "You can trade up to %s different items at the same time this way.", recipient.getName(), TradeSession.ITEM_LIMIT))); + } + + dialog.addSection(new DialogSection() + .setInput(new DialogSelectInput() + .setOptions("Request trade", "Give freely") + .setKey("type"))); + + return dialog; + } + + public static Dialog createRecipientOfferStatusDialog(Player initiator, Map initiatorOffers, Map recipientOffers) { + Dialog dialog = createOfferDialog(String.format("%s's offer:", initiator.getName()), initiatorOffers).setActions("Cancel", "Submit offer"); + dialog.addSection(createOfferSection(recipientOffers).setTitle("Your current offer:")); + + // Add multi-item trading hint if the item limit hasn't been reached yet + if(recipientOffers.size() < TradeSession.ITEM_LIMIT) { + dialog.addSection(new DialogSection() + .setText(String.format("Drag another item to %s to include it in your offer. " + + "You can trade up to %s different items at the same time this way.", initiator.getName(), TradeSession.ITEM_LIMIT))); + } + + return dialog; + } + + public static Dialog createFinalOfferDialog(Player initiator, Player recipient, Map initiatorOffers, Map recipientOffers) { + return createOfferDialog(String.format("%s has offered:", recipient.getName()), recipientOffers).setActions("yesno") + .addSection(createOfferSection(initiatorOffers).setText("For your:")) + .addSection(new DialogSection().setText("Do you accept this trade?")); + } + + public static Dialog createOfferDialog(String title, Map offer) { + return createOfferDialog(title, null, offer); + } + + public static Dialog createOfferDialog(String title, String text, Map offer) { + return createOfferDialog(title, text, null, offer); + } + + public static Dialog createOfferDialog(String title, String text, String footer, Map offer) { + Dialog dialog = new Dialog().addSection(createOfferSection(offer).setTitle(title).setText(text)); + + if(footer != null) { + dialog.addSection(new DialogSection().setText(footer)); + } + + return dialog; + } + + private static DialogSection createOfferSection(Map offers) { + DialogSection section = new DialogSection(); + offers.forEach((item, quantity) -> { + section.addItem(new DialogListItem().setItem(item.getCode()) + .setImage(String.format("inventory/%s", item.getId())) + .setText(String.format("%s x %s", item.getTitle(), quantity))); + }); + return section; + } + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/EntityUseRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/EntityUseRequest.java new file mode 100644 index 00000000..e5654b29 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/EntityUseRequest.java @@ -0,0 +1,44 @@ +package brainwine.gameserver.server.requests; + +import java.util.List; + +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.ItemRegistry; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; + +@RequestInfo(id = 46) +public class EntityUseRequest extends PlayerRequest { + + public int entityId; + public Object data; + + @Override + public void process(Player player) { + Entity entity = player.getZone().getEntity(entityId); + + // Check if entity exists + if(entity == null) { + return; + } + + // Check if entity is player + if(entity.isPlayer()) { + Player targetPlayer = (Player)entity; + + if(data instanceof List) { + List data = (List)this.data; + + // Handle trade + if(data.size() == 2 && "trade".equals(data.get(0)) && data.get(1) instanceof Integer) { + Item item = ItemRegistry.getItem((int)data.get(1)); + player.tradeItem(targetPlayer, item); + } + } + + return; + } + } +} From 22a1366244c0963eb9dedba30ac9531bc08a1001 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Wed, 22 May 2024 19:44:12 +0200 Subject: [PATCH 085/176] Fix commands --- .../main/java/brainwine/gameserver/command/CommandManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/command/CommandManager.java b/gameserver/src/main/java/brainwine/gameserver/command/CommandManager.java index d3a2b994..d53fb05f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/CommandManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/CommandManager.java @@ -40,7 +40,7 @@ public static void init() { private static void registerCommands() { logger.info(SERVER_MARKER, "Registering commands ..."); - Reflections reflections = new Reflections("brainwine.gameserver.commands"); + Reflections reflections = new Reflections("brainwine.gameserver.command"); Set> classes = reflections.getTypesAnnotatedWith(CommandInfo.class); for(Class clazz : classes) { From a55fc3da5efc5ff223425ada7dba799aa3340f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Wed, 29 May 2024 01:31:10 +0200 Subject: [PATCH 086/176] Conveyor belt using behavior tree (#55) --- .../brainwine/gameserver/entity/Entity.java | 6 ++- .../brainwine/gameserver/entity/npc/Npc.java | 9 +++- .../entity/npc/behavior/Behavior.java | 2 + .../behavior/composed/CrawlerBehavior.java | 3 ++ .../npc/behavior/composed/DiggerBehavior.java | 3 ++ .../npc/behavior/composed/WalkerBehavior.java | 3 ++ .../npc/behavior/parts/ConveyorBehavior.java | 50 +++++++++++++++++ .../gameserver/item/ItemUseType.java | 1 + .../java/brainwine/gameserver/zone/Block.java | 4 ++ .../java/brainwine/gameserver/zone/Zone.java | 53 +++++++++---------- 10 files changed, 104 insertions(+), 30 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ConveyorBehavior.java diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java index becacb38..8bb3e79c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java @@ -338,7 +338,11 @@ public int getSizeX() { public int getSizeY() { return sizeY; } - + + public void setDirection(int direction) { + setDirection(direction > 0 ? FacingDirection.EAST : direction < 0 ? FacingDirection.WEST : this.direction); + } + public void setDirection(FacingDirection direction) { this.direction = direction; } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java index e747bde8..10beac9e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java @@ -254,11 +254,18 @@ public void move(int x, int y, String animation) { } public void move(int x, int y, float speed, String animation) { + move(x, y, speed, animation, true); + } + + public void move(int x, int y, float speed, String animation, boolean changeDirection) { this.speed = speed; - direction = x > 0 ? FacingDirection.EAST : x < 0 ? FacingDirection.WEST : direction; moveX = x; moveY = y; + if(changeDirection) { + setDirection(x); + } + if(animation != null) { setAnimation(animation); } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/Behavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/Behavior.java index 36fe0cd8..21756c16 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/Behavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/Behavior.java @@ -13,6 +13,7 @@ import brainwine.gameserver.entity.npc.behavior.composed.FlyerBehavior; import brainwine.gameserver.entity.npc.behavior.composed.WalkerBehavior; import brainwine.gameserver.entity.npc.behavior.parts.ClimbBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.ConveyorBehavior; import brainwine.gameserver.entity.npc.behavior.parts.DigBehavior; import brainwine.gameserver.entity.npc.behavior.parts.EruptionAttackBehavior; import brainwine.gameserver.entity.npc.behavior.parts.FallBehavior; @@ -46,6 +47,7 @@ @Type(name = "walk", value = WalkBehavior.class), @Type(name = "fall", value = FallBehavior.class), @Type(name = "turn", value = TurnBehavior.class), + @Type(name = "conveyor", value = ConveyorBehavior.class), @Type(name = "follow", value = FollowBehavior.class), @Type(name = "climb", value = ClimbBehavior.class), @Type(name = "dig", value = DigBehavior.class), diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/CrawlerBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/CrawlerBehavior.java index 4c2821d6..46f98e62 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/CrawlerBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/CrawlerBehavior.java @@ -8,6 +8,7 @@ import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.entity.npc.behavior.SelectorBehavior; import brainwine.gameserver.entity.npc.behavior.parts.ClimbBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.ConveyorBehavior; import brainwine.gameserver.entity.npc.behavior.parts.FallBehavior; import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior; import brainwine.gameserver.entity.npc.behavior.parts.TurnBehavior; @@ -28,6 +29,8 @@ public CrawlerBehavior(Npc entity) { @Override public void addChildren(Map config) { + addChild(ConveyorBehavior.class, config); + if(config.containsKey("idle")) { addChild(IdleBehavior.class, MapHelper.getMap(config, "idle")); } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/DiggerBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/DiggerBehavior.java index bf95bf7e..3cd50486 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/DiggerBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/DiggerBehavior.java @@ -7,6 +7,7 @@ import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.entity.npc.behavior.SelectorBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.ConveyorBehavior; import brainwine.gameserver.entity.npc.behavior.parts.DigBehavior; import brainwine.gameserver.entity.npc.behavior.parts.FallBehavior; import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior; @@ -28,6 +29,8 @@ public DiggerBehavior(Npc entity) { @Override public void addChildren(Map config) { + addChild(ConveyorBehavior.class, config); + if(config.containsKey("idle")) { addChild(IdleBehavior.class, MapHelper.getMap(config, "idle")); } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/WalkerBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/WalkerBehavior.java index 15eb95d0..ddd9dcb1 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/WalkerBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/WalkerBehavior.java @@ -7,6 +7,7 @@ import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.entity.npc.behavior.SelectorBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.ConveyorBehavior; import brainwine.gameserver.entity.npc.behavior.parts.FallBehavior; import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior; import brainwine.gameserver.entity.npc.behavior.parts.TurnBehavior; @@ -27,6 +28,8 @@ public WalkerBehavior(Npc entity) { @Override protected void addChildren(Map config) { + addChild(ConveyorBehavior.class, config); + if(config.containsKey("idle")) { addChild(IdleBehavior.class, MapHelper.getMap(config, "idle")); } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ConveyorBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ConveyorBehavior.java new file mode 100644 index 00000000..6e82a51b --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ConveyorBehavior.java @@ -0,0 +1,50 @@ +package brainwine.gameserver.entity.npc.behavior.parts; + +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; + +import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; +import brainwine.gameserver.item.ItemUseType; +import brainwine.gameserver.zone.Block; + +public class ConveyorBehavior extends Behavior { + + protected String animation = "idle"; + protected Block conveyorBlock; + + @JsonCreator + public ConveyorBehavior(@JacksonInject Npc entity) { + super(entity); + } + + @Override + public boolean behave() { + int direction = conveyorBlock.getFrontMod() == 0 ? 1 : -1; + float movingSurfacePower = conveyorBlock.getFrontItem().getPower(); + + // Randomly change direction to match the conveyor belt's + if(Math.random() < 0.333) { + entity.setDirection(direction); + } + + // Fail if entity is blocked to allow for other behavior like crawling to work + if(entity.isBlocked(direction, 0)) { + return false; + } + + entity.move(direction, 0, movingSurfacePower, animation, false); + return true; + } + + @Override + public boolean canBehave() { + // Check if entity is standing on a conveyor belt + conveyorBlock = entity.getZone().findBlock(entity.getBlockX(), entity.getBlockY() + 1, block -> block.getFrontItem().hasUse(ItemUseType.MOVE)); + return conveyorBlock != null; + } + + public void setAnimation(String animation) { + this.animation = animation; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java index 0e95ce7d..e05f7a9b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java @@ -38,6 +38,7 @@ public enum ItemUseType { CHANGE(new ChangeInteraction()), FIELDABLE, FLY, + MOVE, MULTI, NOTE(new NoteInteraction()), PET, diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Block.java b/gameserver/src/main/java/brainwine/gameserver/zone/Block.java index aa71fbcc..7d3db1db 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Block.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Block.java @@ -34,6 +34,10 @@ public Block(Item baseItem, Item backItem, int backMod, Item frontItem, int fron updateLayer(Layer.FRONT, frontItem, frontMod, ownerHash); updateLayer(Layer.LIQUID, liquidItem, liquidMod, ownerHash); } + + public boolean isSolid() { + return (frontItem.isDoor() && frontMod % 2 == 0) || (!frontItem.isDoor() && frontItem.isSolid()); + } public void updateLayer(Layer layer, int item) { updateLayer(layer, item, 0); diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index bb7c1de5..21dba285 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -543,38 +543,35 @@ public boolean isBlockSolid(int x, int y, boolean checkAdjacents) { } Block block = getBlock(x, y); - Item item = block.getItem(Layer.FRONT); - - if(item.isDoor() && block.getFrontMod() % 2 == 0) { - return true; - } else if(!item.isDoor() && item.isSolid()) { - return true; - } - - if(checkAdjacents) { - for(int i = -3; i <= 0; i++) { - for(int j = 0; j <= 2; j++) { - int x1 = x + i; - int y1 = y + j; - - if(!areCoordinatesInBounds(x1, y1) || !isChunkLoaded(x1, y1)) { - continue; - } - - block = getBlock(x1, y1); - item = block.getFrontItem(); - - if(item.getBlockWidth() > Math.abs(i) && item.getBlockHeight() > Math.abs(j) - && isBlockSolid(x1, y1, false)) { - return true; - } + return block.isSolid() || (checkAdjacents && findBlock(x, y, Block::isSolid) != null); + } + + /** + * Find block with item occupying the block position that satisfies the predicate. + * Closer blocks are prioritized in row major order. + */ + public Block findBlock(int x, int y, Predicate predicate) { + for(int i = 0; i >= -3; i--) { + for(int j = 0; j <= 2; j++) { + int x1 = x + i; + int y1 = y + j; + + if(!areCoordinatesInBounds(x1, y1) || !isChunkLoaded(x1, y1)) { + continue; + } + + Block block = getBlock(x1, y1); + Item item = block.getFrontItem(); + + if(item.getBlockWidth() > Math.abs(i) && item.getBlockHeight() > Math.abs(j) && predicate.test(block)) { + return block; } } } - - return false; + + return null; } - + public boolean isBlockOccupied(int x, int y, Layer layer) { if(!areCoordinatesInBounds(x, y)) { return false; From 47be50e21a053acd78bcc1c113f0ab4334e53521 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 4 Jul 2024 19:24:35 +0200 Subject: [PATCH 087/176] Commit progress --- .../brainwine/gameserver/entity/npc/Npc.java | 9 +++ .../entity/npc/behavior/Behavior.java | 28 +++++++- .../entity/npc/behavior/BehaviorMessage.java | 12 ++++ .../npc/behavior/CompositeBehavior.java | 14 +++- .../behavior/composed/QuesterBehavior.java | 35 ++++++++++ .../npc/behavior/parts/ChatterBehavior.java | 69 +++++++++++++++++++ .../npc/behavior/parts/DialoguerBehavior.java | 50 ++++++++++++++ .../npc/behavior/parts/IdleBehavior.java | 2 + .../npc/behavior/parts/PetBehavior.java | 45 ++++++++++++ .../server/requests/EntityUseRequest.java | 15 ++++ 10 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/BehaviorMessage.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/QuesterBehavior.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ChatterBehavior.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/DialoguerBehavior.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/PetBehavior.java diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java index e747bde8..239d651c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java @@ -16,6 +16,7 @@ import brainwine.gameserver.entity.EntityLoot; import brainwine.gameserver.entity.EntityRegistry; import brainwine.gameserver.entity.FacingDirection; +import brainwine.gameserver.entity.npc.behavior.BehaviorMessage; import brainwine.gameserver.entity.npc.behavior.SequenceBehavior; import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; @@ -241,6 +242,14 @@ public Map getStatusConfig() { return config; } + public void interact(Player player, Object... data) { + interact(BehaviorMessage.INTERACT, player, data); + } + + public void interact(BehaviorMessage message, Player player, Object... data) { + behaviorTree.react(message, player, data); + } + public void move(int x, int y) { move(x, y, baseSpeed); } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/Behavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/Behavior.java index 36fe0cd8..1e228eda 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/Behavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/Behavior.java @@ -11,8 +11,11 @@ import brainwine.gameserver.entity.npc.behavior.composed.CrawlerBehavior; import brainwine.gameserver.entity.npc.behavior.composed.DiggerBehavior; import brainwine.gameserver.entity.npc.behavior.composed.FlyerBehavior; +import brainwine.gameserver.entity.npc.behavior.composed.QuesterBehavior; import brainwine.gameserver.entity.npc.behavior.composed.WalkerBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.ChatterBehavior; import brainwine.gameserver.entity.npc.behavior.parts.ClimbBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.DialoguerBehavior; import brainwine.gameserver.entity.npc.behavior.parts.DigBehavior; import brainwine.gameserver.entity.npc.behavior.parts.EruptionAttackBehavior; import brainwine.gameserver.entity.npc.behavior.parts.FallBehavior; @@ -20,6 +23,7 @@ import brainwine.gameserver.entity.npc.behavior.parts.FlyTowardBehavior; import brainwine.gameserver.entity.npc.behavior.parts.FollowBehavior; import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.PetBehavior; import brainwine.gameserver.entity.npc.behavior.parts.RandomlyTargetBehavior; import brainwine.gameserver.entity.npc.behavior.parts.ReporterBehavior; import brainwine.gameserver.entity.npc.behavior.parts.ShielderBehavior; @@ -27,6 +31,7 @@ import brainwine.gameserver.entity.npc.behavior.parts.TurnBehavior; import brainwine.gameserver.entity.npc.behavior.parts.UnblockBehavior; import brainwine.gameserver.entity.npc.behavior.parts.WalkBehavior; +import brainwine.gameserver.player.Player; /** * Heavily based on Deepworld's original "rubyhave" (ha ha very punny) behavior system. @@ -41,6 +46,7 @@ @Type(name = "crawler", value = CrawlerBehavior.class), @Type(name = "flyer", value = FlyerBehavior.class), @Type(name = "digger", value = DiggerBehavior.class), + @Type(name = "quester", value = QuesterBehavior.class), // Parts @Type(name = "idle", value = IdleBehavior.class), @Type(name = "walk", value = WalkBehavior.class), @@ -56,12 +62,16 @@ @Type(name = "eruption_attack", value = EruptionAttackBehavior.class), @Type(name = "randomly_target", value = RandomlyTargetBehavior.class), @Type(name = "reporter", value = ReporterBehavior.class), - @Type(name = "unblock", value = UnblockBehavior.class) + @Type(name = "unblock", value = UnblockBehavior.class), + @Type(name = "pet", value = PetBehavior.class), + @Type(name = "chatter", value = ChatterBehavior.class), + @Type(name = "dialoguer", value = DialoguerBehavior.class) }) @JsonIgnoreProperties(ignoreUnknown = true) public abstract class Behavior { protected final Npc entity; + private CompositeBehavior parent; public Behavior(Npc entity) { this.entity = entity; @@ -69,7 +79,23 @@ public Behavior(Npc entity) { public abstract boolean behave(); + public final void react(BehaviorMessage message, Object... data) { + react(message, null, data); + } + + public void react(BehaviorMessage message, Player player, Object... data) { + // Override + } + public boolean canBehave() { return true; } + + protected final void setParent(CompositeBehavior parent) { + this.parent = parent; + } + + public CompositeBehavior getParent() { + return parent; + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/BehaviorMessage.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/BehaviorMessage.java new file mode 100644 index 00000000..33760578 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/BehaviorMessage.java @@ -0,0 +1,12 @@ +package brainwine.gameserver.entity.npc.behavior; + +/** + * External events that can influence NPC behavior. + */ +public enum BehaviorMessage { + + ANGER, + DAMAGE, + DEATH, + INTERACT +} diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/CompositeBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/CompositeBehavior.java index ef075088..bbe44b04 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/CompositeBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/CompositeBehavior.java @@ -15,6 +15,7 @@ import com.fasterxml.jackson.databind.InjectableValues; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.player.Player; import brainwine.shared.JsonHelper; public abstract class CompositeBehavior extends Behavior { @@ -31,6 +32,14 @@ public CompositeBehavior(Npc entity) { this(entity, Collections.emptyMap()); } + @Override + public void react(BehaviorMessage message, Player player, Object... data) { + // Child behavior might add new behavior to its parent so we loop like this to avoid ConcurrentModificationException + for(int i = 0; i < children.size(); i++) { + children.get(i).react(message, player, data); + } + } + protected void addChildren(Map config) { // Override } @@ -49,11 +58,14 @@ public void addChild(Behavior child) { return; } + child.setParent(this); children.add(child); } public void removeChild(Behavior child) { - children.remove(child); + if(children.remove(child)) { + child.setParent(null); + } } public Collection getChildren() { diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/QuesterBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/QuesterBehavior.java new file mode 100644 index 00000000..afc0763e --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/QuesterBehavior.java @@ -0,0 +1,35 @@ +package brainwine.gameserver.entity.npc.behavior.composed; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; + +import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.SelectorBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.ChatterBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.DialoguerBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.FallBehavior; +import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior; + +public class QuesterBehavior extends SelectorBehavior { + + @JsonCreator + private QuesterBehavior(@JacksonInject Npc entity, + Map config) { + super(entity, config); + } + + public QuesterBehavior(Npc entity) { + super(entity); + } + + @Override + public void addChildren(Map config) { + addChild(FallBehavior.class, config); + addChild(DialoguerBehavior.class, config); + addChild(ChatterBehavior.class, config); + addChild(IdleBehavior.class, config); + addChild(WalkerBehavior.class, config); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ChatterBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ChatterBehavior.java new file mode 100644 index 00000000..504845ce --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ChatterBehavior.java @@ -0,0 +1,69 @@ +package brainwine.gameserver.entity.npc.behavior.parts; + +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; + +import brainwine.gameserver.entity.FacingDirection; +import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; +import brainwine.gameserver.player.Player; + +public class ChatterBehavior extends Behavior { + + public static final long BEHAVIOR_COOLDOWN = 6000; + public static final long CHAT_COOLDOWN = 60000; + private final Map recentChats = new HashMap<>(); + private String nextMessage; + private long nextMessageAt; + private long lastChattedAt; + + @JsonCreator + public ChatterBehavior(@JacksonInject Npc entity) { + super(entity); + } + + @Override + public boolean behave() { + long now = System.currentTimeMillis(); + + // Emote the next message if it is ready + if(nextMessage != null && now >= nextMessageAt) { + entity.emote(nextMessage); + nextMessage = null; + } + + // Stop for a little while if the entity has chatted recently + if(now < lastChattedAt + BEHAVIOR_COOLDOWN) { + return true; + } + + recentChats.values().removeIf(x -> now > x + CHAT_COOLDOWN); // Clear expired entries + Player player = entity.getZone().getRandomPlayerInRange(entity.getX(), entity.getY(), 3); + + // Fail if no player is nearby or entity has chatted with target player recently + if(player == null || recentChats.containsKey(player)) { + return false; + } + + // TODO store messages in a server configuration along with entity & zone names + String[] messages = { + "Hello.", + "Salutations.", + "Acknowledgements, stranger.", + "Ah, a human!", + "Hello, survivor person.", + "Good day.", + "Hello human." + }; + nextMessage = messages[(int)(Math.random() * messages.length)]; + nextMessageAt = now + 1000; // Give the entity some time to stop moving + lastChattedAt = now; + recentChats.put(player, lastChattedAt); + entity.setDirection(entity.getX() > player.getX() ? FacingDirection.WEST : FacingDirection.EAST); // Face entity towards the player + entity.setAnimation(0); + return true; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/DialoguerBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/DialoguerBehavior.java new file mode 100644 index 00000000..9452fe25 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/DialoguerBehavior.java @@ -0,0 +1,50 @@ +package brainwine.gameserver.entity.npc.behavior.parts; + +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; + +import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; +import brainwine.gameserver.entity.npc.behavior.BehaviorMessage; +import brainwine.gameserver.player.Player; + +// TODO implement this fully +public class DialoguerBehavior extends Behavior { + + @JsonCreator + public DialoguerBehavior(@JacksonInject Npc entity) { + super(entity); + } + + @Override + public boolean behave() { + return false; + } + + @Override + public void react(BehaviorMessage message, Player player, Object... data) { + switch(message) { + case INTERACT: + // Do nothing if player is null + if(player == null) { + break; + } + + String[] responses = { + "Error: Job module not found.", + "I am not quite ready for that yet.", + "Sorry, please try again later.", + "Query returned error 404.", + "Critical error.", + "Does not compute." + }; + + // Respond with a random bogus message for now + String response = responses[(int)(Math.random() * responses.length)]; + entity.emote(response); + break; + default: + break; + } + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/IdleBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/IdleBehavior.java index d9f95979..2db95e06 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/IdleBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/IdleBehavior.java @@ -4,6 +4,7 @@ import java.util.concurrent.ThreadLocalRandom; import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonSetter; @@ -86,6 +87,7 @@ public void setGroundOffset(Vector2i groundOffset) { } @JsonSetter("animation") + @JsonAlias("idle_animation") public void setAnimations(String... animations) { this.animations = animations; } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/PetBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/PetBehavior.java new file mode 100644 index 00000000..be0db882 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/PetBehavior.java @@ -0,0 +1,45 @@ +package brainwine.gameserver.entity.npc.behavior.parts; + +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; + +import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.entity.npc.behavior.Behavior; +import brainwine.gameserver.entity.npc.behavior.BehaviorMessage; +import brainwine.gameserver.player.Player; + +public class PetBehavior extends Behavior { + + public static final long PET_COOLDOWN = 2000; + private long lastPettedAt; + + @JsonCreator + public PetBehavior(@JacksonInject Npc entity) { + super(entity); + } + + @Override + public boolean behave() { + return true; + } + + @Override + public void react(BehaviorMessage message, Player player, Object... data) { + switch(message) { + case INTERACT: + long now = System.currentTimeMillis(); + + // Do nothing if entity was petted recently + if(now < lastPettedAt + PET_COOLDOWN) { + return; + } + + entity.emote("*terrapus noises*"); + entity.spawnEffect("terrapus purr"); + lastPettedAt = now; + break; + default: + break; + } + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/EntityUseRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/EntityUseRequest.java index e5654b29..5587f2d6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/EntityUseRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/EntityUseRequest.java @@ -3,6 +3,7 @@ import java.util.List; import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.player.Player; @@ -17,6 +18,11 @@ public class EntityUseRequest extends PlayerRequest { @Override public void process(Player player) { + // Do nothing if player is dead + if(player.isDead()) { + return; + } + Entity entity = player.getZone().getEntity(entityId); // Check if entity exists @@ -40,5 +46,14 @@ public void process(Player player) { return; } + + // Handle NPC interaction + Npc npc = (Npc)entity; + + if(data instanceof List) { + npc.interact(player, ((List)data).toArray()); + } else { + npc.interact(player, data); + } } } From a60652cbf3a38c679aea33351f2ad484c62106a1 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 9 Jul 2024 19:55:16 +0200 Subject: [PATCH 088/176] Revert "Merge branch 'feature/android-behavior' into develop" This reverts commit 69da62811ba28157dac4845c773a3285677bffae, reversing changes made to a55fc3da5efc5ff223425ada7dba799aa3340f9c. --- .../brainwine/gameserver/entity/npc/Npc.java | 9 --- .../entity/npc/behavior/Behavior.java | 28 +------- .../entity/npc/behavior/BehaviorMessage.java | 12 ---- .../npc/behavior/CompositeBehavior.java | 14 +--- .../behavior/composed/QuesterBehavior.java | 35 ---------- .../npc/behavior/parts/ChatterBehavior.java | 69 ------------------- .../npc/behavior/parts/DialoguerBehavior.java | 50 -------------- .../npc/behavior/parts/IdleBehavior.java | 2 - .../npc/behavior/parts/PetBehavior.java | 45 ------------ .../server/requests/EntityUseRequest.java | 15 ---- 10 files changed, 2 insertions(+), 277 deletions(-) delete mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/BehaviorMessage.java delete mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/QuesterBehavior.java delete mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ChatterBehavior.java delete mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/DialoguerBehavior.java delete mode 100644 gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/PetBehavior.java diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java index ffd12337..10beac9e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java @@ -16,7 +16,6 @@ import brainwine.gameserver.entity.EntityLoot; import brainwine.gameserver.entity.EntityRegistry; import brainwine.gameserver.entity.FacingDirection; -import brainwine.gameserver.entity.npc.behavior.BehaviorMessage; import brainwine.gameserver.entity.npc.behavior.SequenceBehavior; import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; @@ -242,14 +241,6 @@ public Map getStatusConfig() { return config; } - public void interact(Player player, Object... data) { - interact(BehaviorMessage.INTERACT, player, data); - } - - public void interact(BehaviorMessage message, Player player, Object... data) { - behaviorTree.react(message, player, data); - } - public void move(int x, int y) { move(x, y, baseSpeed); } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/Behavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/Behavior.java index 92887a29..21756c16 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/Behavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/Behavior.java @@ -11,12 +11,9 @@ import brainwine.gameserver.entity.npc.behavior.composed.CrawlerBehavior; import brainwine.gameserver.entity.npc.behavior.composed.DiggerBehavior; import brainwine.gameserver.entity.npc.behavior.composed.FlyerBehavior; -import brainwine.gameserver.entity.npc.behavior.composed.QuesterBehavior; import brainwine.gameserver.entity.npc.behavior.composed.WalkerBehavior; -import brainwine.gameserver.entity.npc.behavior.parts.ChatterBehavior; import brainwine.gameserver.entity.npc.behavior.parts.ClimbBehavior; import brainwine.gameserver.entity.npc.behavior.parts.ConveyorBehavior; -import brainwine.gameserver.entity.npc.behavior.parts.DialoguerBehavior; import brainwine.gameserver.entity.npc.behavior.parts.DigBehavior; import brainwine.gameserver.entity.npc.behavior.parts.EruptionAttackBehavior; import brainwine.gameserver.entity.npc.behavior.parts.FallBehavior; @@ -24,7 +21,6 @@ import brainwine.gameserver.entity.npc.behavior.parts.FlyTowardBehavior; import brainwine.gameserver.entity.npc.behavior.parts.FollowBehavior; import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior; -import brainwine.gameserver.entity.npc.behavior.parts.PetBehavior; import brainwine.gameserver.entity.npc.behavior.parts.RandomlyTargetBehavior; import brainwine.gameserver.entity.npc.behavior.parts.ReporterBehavior; import brainwine.gameserver.entity.npc.behavior.parts.ShielderBehavior; @@ -32,7 +28,6 @@ import brainwine.gameserver.entity.npc.behavior.parts.TurnBehavior; import brainwine.gameserver.entity.npc.behavior.parts.UnblockBehavior; import brainwine.gameserver.entity.npc.behavior.parts.WalkBehavior; -import brainwine.gameserver.player.Player; /** * Heavily based on Deepworld's original "rubyhave" (ha ha very punny) behavior system. @@ -47,7 +42,6 @@ @Type(name = "crawler", value = CrawlerBehavior.class), @Type(name = "flyer", value = FlyerBehavior.class), @Type(name = "digger", value = DiggerBehavior.class), - @Type(name = "quester", value = QuesterBehavior.class), // Parts @Type(name = "idle", value = IdleBehavior.class), @Type(name = "walk", value = WalkBehavior.class), @@ -64,16 +58,12 @@ @Type(name = "eruption_attack", value = EruptionAttackBehavior.class), @Type(name = "randomly_target", value = RandomlyTargetBehavior.class), @Type(name = "reporter", value = ReporterBehavior.class), - @Type(name = "unblock", value = UnblockBehavior.class), - @Type(name = "pet", value = PetBehavior.class), - @Type(name = "chatter", value = ChatterBehavior.class), - @Type(name = "dialoguer", value = DialoguerBehavior.class) + @Type(name = "unblock", value = UnblockBehavior.class) }) @JsonIgnoreProperties(ignoreUnknown = true) public abstract class Behavior { protected final Npc entity; - private CompositeBehavior parent; public Behavior(Npc entity) { this.entity = entity; @@ -81,23 +71,7 @@ public Behavior(Npc entity) { public abstract boolean behave(); - public final void react(BehaviorMessage message, Object... data) { - react(message, null, data); - } - - public void react(BehaviorMessage message, Player player, Object... data) { - // Override - } - public boolean canBehave() { return true; } - - protected final void setParent(CompositeBehavior parent) { - this.parent = parent; - } - - public CompositeBehavior getParent() { - return parent; - } } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/BehaviorMessage.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/BehaviorMessage.java deleted file mode 100644 index 33760578..00000000 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/BehaviorMessage.java +++ /dev/null @@ -1,12 +0,0 @@ -package brainwine.gameserver.entity.npc.behavior; - -/** - * External events that can influence NPC behavior. - */ -public enum BehaviorMessage { - - ANGER, - DAMAGE, - DEATH, - INTERACT -} diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/CompositeBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/CompositeBehavior.java index bbe44b04..ef075088 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/CompositeBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/CompositeBehavior.java @@ -15,7 +15,6 @@ import com.fasterxml.jackson.databind.InjectableValues; import brainwine.gameserver.entity.npc.Npc; -import brainwine.gameserver.player.Player; import brainwine.shared.JsonHelper; public abstract class CompositeBehavior extends Behavior { @@ -32,14 +31,6 @@ public CompositeBehavior(Npc entity) { this(entity, Collections.emptyMap()); } - @Override - public void react(BehaviorMessage message, Player player, Object... data) { - // Child behavior might add new behavior to its parent so we loop like this to avoid ConcurrentModificationException - for(int i = 0; i < children.size(); i++) { - children.get(i).react(message, player, data); - } - } - protected void addChildren(Map config) { // Override } @@ -58,14 +49,11 @@ public void addChild(Behavior child) { return; } - child.setParent(this); children.add(child); } public void removeChild(Behavior child) { - if(children.remove(child)) { - child.setParent(null); - } + children.remove(child); } public Collection getChildren() { diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/QuesterBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/QuesterBehavior.java deleted file mode 100644 index afc0763e..00000000 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/composed/QuesterBehavior.java +++ /dev/null @@ -1,35 +0,0 @@ -package brainwine.gameserver.entity.npc.behavior.composed; - -import java.util.Map; - -import com.fasterxml.jackson.annotation.JacksonInject; -import com.fasterxml.jackson.annotation.JsonCreator; - -import brainwine.gameserver.entity.npc.Npc; -import brainwine.gameserver.entity.npc.behavior.SelectorBehavior; -import brainwine.gameserver.entity.npc.behavior.parts.ChatterBehavior; -import brainwine.gameserver.entity.npc.behavior.parts.DialoguerBehavior; -import brainwine.gameserver.entity.npc.behavior.parts.FallBehavior; -import brainwine.gameserver.entity.npc.behavior.parts.IdleBehavior; - -public class QuesterBehavior extends SelectorBehavior { - - @JsonCreator - private QuesterBehavior(@JacksonInject Npc entity, - Map config) { - super(entity, config); - } - - public QuesterBehavior(Npc entity) { - super(entity); - } - - @Override - public void addChildren(Map config) { - addChild(FallBehavior.class, config); - addChild(DialoguerBehavior.class, config); - addChild(ChatterBehavior.class, config); - addChild(IdleBehavior.class, config); - addChild(WalkerBehavior.class, config); - } -} diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ChatterBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ChatterBehavior.java deleted file mode 100644 index 504845ce..00000000 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/ChatterBehavior.java +++ /dev/null @@ -1,69 +0,0 @@ -package brainwine.gameserver.entity.npc.behavior.parts; - -import java.util.HashMap; -import java.util.Map; - -import com.fasterxml.jackson.annotation.JacksonInject; -import com.fasterxml.jackson.annotation.JsonCreator; - -import brainwine.gameserver.entity.FacingDirection; -import brainwine.gameserver.entity.npc.Npc; -import brainwine.gameserver.entity.npc.behavior.Behavior; -import brainwine.gameserver.player.Player; - -public class ChatterBehavior extends Behavior { - - public static final long BEHAVIOR_COOLDOWN = 6000; - public static final long CHAT_COOLDOWN = 60000; - private final Map recentChats = new HashMap<>(); - private String nextMessage; - private long nextMessageAt; - private long lastChattedAt; - - @JsonCreator - public ChatterBehavior(@JacksonInject Npc entity) { - super(entity); - } - - @Override - public boolean behave() { - long now = System.currentTimeMillis(); - - // Emote the next message if it is ready - if(nextMessage != null && now >= nextMessageAt) { - entity.emote(nextMessage); - nextMessage = null; - } - - // Stop for a little while if the entity has chatted recently - if(now < lastChattedAt + BEHAVIOR_COOLDOWN) { - return true; - } - - recentChats.values().removeIf(x -> now > x + CHAT_COOLDOWN); // Clear expired entries - Player player = entity.getZone().getRandomPlayerInRange(entity.getX(), entity.getY(), 3); - - // Fail if no player is nearby or entity has chatted with target player recently - if(player == null || recentChats.containsKey(player)) { - return false; - } - - // TODO store messages in a server configuration along with entity & zone names - String[] messages = { - "Hello.", - "Salutations.", - "Acknowledgements, stranger.", - "Ah, a human!", - "Hello, survivor person.", - "Good day.", - "Hello human." - }; - nextMessage = messages[(int)(Math.random() * messages.length)]; - nextMessageAt = now + 1000; // Give the entity some time to stop moving - lastChattedAt = now; - recentChats.put(player, lastChattedAt); - entity.setDirection(entity.getX() > player.getX() ? FacingDirection.WEST : FacingDirection.EAST); // Face entity towards the player - entity.setAnimation(0); - return true; - } -} diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/DialoguerBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/DialoguerBehavior.java deleted file mode 100644 index 9452fe25..00000000 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/DialoguerBehavior.java +++ /dev/null @@ -1,50 +0,0 @@ -package brainwine.gameserver.entity.npc.behavior.parts; - -import com.fasterxml.jackson.annotation.JacksonInject; -import com.fasterxml.jackson.annotation.JsonCreator; - -import brainwine.gameserver.entity.npc.Npc; -import brainwine.gameserver.entity.npc.behavior.Behavior; -import brainwine.gameserver.entity.npc.behavior.BehaviorMessage; -import brainwine.gameserver.player.Player; - -// TODO implement this fully -public class DialoguerBehavior extends Behavior { - - @JsonCreator - public DialoguerBehavior(@JacksonInject Npc entity) { - super(entity); - } - - @Override - public boolean behave() { - return false; - } - - @Override - public void react(BehaviorMessage message, Player player, Object... data) { - switch(message) { - case INTERACT: - // Do nothing if player is null - if(player == null) { - break; - } - - String[] responses = { - "Error: Job module not found.", - "I am not quite ready for that yet.", - "Sorry, please try again later.", - "Query returned error 404.", - "Critical error.", - "Does not compute." - }; - - // Respond with a random bogus message for now - String response = responses[(int)(Math.random() * responses.length)]; - entity.emote(response); - break; - default: - break; - } - } -} diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/IdleBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/IdleBehavior.java index 2db95e06..d9f95979 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/IdleBehavior.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/IdleBehavior.java @@ -4,7 +4,6 @@ import java.util.concurrent.ThreadLocalRandom; import com.fasterxml.jackson.annotation.JacksonInject; -import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonSetter; @@ -87,7 +86,6 @@ public void setGroundOffset(Vector2i groundOffset) { } @JsonSetter("animation") - @JsonAlias("idle_animation") public void setAnimations(String... animations) { this.animations = animations; } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/PetBehavior.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/PetBehavior.java deleted file mode 100644 index be0db882..00000000 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/behavior/parts/PetBehavior.java +++ /dev/null @@ -1,45 +0,0 @@ -package brainwine.gameserver.entity.npc.behavior.parts; - -import com.fasterxml.jackson.annotation.JacksonInject; -import com.fasterxml.jackson.annotation.JsonCreator; - -import brainwine.gameserver.entity.npc.Npc; -import brainwine.gameserver.entity.npc.behavior.Behavior; -import brainwine.gameserver.entity.npc.behavior.BehaviorMessage; -import brainwine.gameserver.player.Player; - -public class PetBehavior extends Behavior { - - public static final long PET_COOLDOWN = 2000; - private long lastPettedAt; - - @JsonCreator - public PetBehavior(@JacksonInject Npc entity) { - super(entity); - } - - @Override - public boolean behave() { - return true; - } - - @Override - public void react(BehaviorMessage message, Player player, Object... data) { - switch(message) { - case INTERACT: - long now = System.currentTimeMillis(); - - // Do nothing if entity was petted recently - if(now < lastPettedAt + PET_COOLDOWN) { - return; - } - - entity.emote("*terrapus noises*"); - entity.spawnEffect("terrapus purr"); - lastPettedAt = now; - break; - default: - break; - } - } -} diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/EntityUseRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/EntityUseRequest.java index 5587f2d6..e5654b29 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/EntityUseRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/EntityUseRequest.java @@ -3,7 +3,6 @@ import java.util.List; import brainwine.gameserver.entity.Entity; -import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.player.Player; @@ -18,11 +17,6 @@ public class EntityUseRequest extends PlayerRequest { @Override public void process(Player player) { - // Do nothing if player is dead - if(player.isDead()) { - return; - } - Entity entity = player.getZone().getEntity(entityId); // Check if entity exists @@ -46,14 +40,5 @@ public void process(Player player) { return; } - - // Handle NPC interaction - Npc npc = (Npc)entity; - - if(data instanceof List) { - npc.interact(player, ((List)data).toArray()); - } else { - npc.interact(player, data); - } } } From f3012f18ac374d128d785c81c1f24252b629f0df Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Wed, 18 Sep 2024 20:01:02 +0200 Subject: [PATCH 089/176] Throttle `/genzone` command --- .../gameserver/command/admin/GenerateZoneCommand.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/command/admin/GenerateZoneCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/GenerateZoneCommand.java index 1de51dba..e281dcdb 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/admin/GenerateZoneCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/GenerateZoneCommand.java @@ -17,6 +17,7 @@ public class GenerateZoneCommand extends Command { public static final int MIN_HEIGHT = 200; public static final int MAX_WIDTH = 4000; public static final int MAX_HEIGHT = 1600; + private boolean generating; @Override public void execute(CommandExecutor executor, String[] args) { @@ -30,6 +31,11 @@ public void execute(CommandExecutor executor, String[] args) { return; } + if(generating) { + executor.notify("Already generating a zone, please try again in a moment.", SYSTEM); + return; + } + if(args.length >= 3) { try { width = Integer.parseInt(args[1]); @@ -76,6 +82,7 @@ public void execute(CommandExecutor executor, String[] args) { } } + generating = true; executor.notify("Your zone is being generated. It should be ready soon!", SYSTEM); generator.generateZoneAsync(biome, width, height, seed, zone -> { if(zone == null) { @@ -84,6 +91,8 @@ public void execute(CommandExecutor executor, String[] args) { GameServer.getInstance().getZoneManager().addZone(zone); executor.notify(String.format("Your zone '%s' is ready for exploration!", zone.getName()), SYSTEM); } + + generating = false; }); } From 2e8527de950e963b1fc4aa121e17e4fe607fcbc5 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Wed, 18 Sep 2024 20:01:29 +0200 Subject: [PATCH 090/176] Add patch data for various old game versions --- src/main/resources/patches/deepworld-0.5.5.patch | 7 +++++++ src/main/resources/patches/deepworld-0.7.4.patch | 7 +++++++ src/main/resources/patches/deepworld-0.7.5.patch | 7 +++++++ 3 files changed, 21 insertions(+) create mode 100644 src/main/resources/patches/deepworld-0.5.5.patch create mode 100644 src/main/resources/patches/deepworld-0.7.4.patch create mode 100644 src/main/resources/patches/deepworld-0.7.5.patch diff --git a/src/main/resources/patches/deepworld-0.5.5.patch b/src/main/resources/patches/deepworld-0.5.5.patch new file mode 100644 index 00000000..c19787b9 --- /dev/null +++ b/src/main/resources/patches/deepworld-0.5.5.patch @@ -0,0 +1,7 @@ +# Binary info +binary_name Deepworld +binary_size 0x33E394 + +# Addresses +location gateway_host 0x0012B7E5 +location gateway_host_strlen 0x001E3EC0 diff --git a/src/main/resources/patches/deepworld-0.7.4.patch b/src/main/resources/patches/deepworld-0.7.4.patch new file mode 100644 index 00000000..11405afd --- /dev/null +++ b/src/main/resources/patches/deepworld-0.7.4.patch @@ -0,0 +1,7 @@ +# Binary info +binary_name Deepworld +binary_size 0x367DA0 + +# Addresses +location gateway_host 0x0013EB0B +location gateway_host_strlen 0x001FF3C8 diff --git a/src/main/resources/patches/deepworld-0.7.5.patch b/src/main/resources/patches/deepworld-0.7.5.patch new file mode 100644 index 00000000..de546f73 --- /dev/null +++ b/src/main/resources/patches/deepworld-0.7.5.patch @@ -0,0 +1,7 @@ +# Binary info +binary_name Deepworld +binary_size 0x36CC68 + +# Addresses +location gateway_host 0x0013FD46 +location gateway_host_strlen 0x002017F0 From 4f3154a5cc67849347f3354083016d6abace4dfe Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Wed, 18 Sep 2024 20:02:22 +0200 Subject: [PATCH 091/176] Remove unused `tick()` function --- gameserver/src/main/java/brainwine/gameserver/GameServer.java | 1 - .../main/java/brainwine/gameserver/player/PlayerManager.java | 4 ---- 2 files changed, 5 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/GameServer.java b/gameserver/src/main/java/brainwine/gameserver/GameServer.java index b5f804fa..ebb8f253 100644 --- a/gameserver/src/main/java/brainwine/gameserver/GameServer.java +++ b/gameserver/src/main/java/brainwine/gameserver/GameServer.java @@ -92,7 +92,6 @@ public void tick() { } zoneManager.tick(deltaTime); - playerManager.tick(); } /** diff --git a/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java index 4e70c8ff..1e928efb 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java @@ -33,10 +33,6 @@ public PlayerManager() { loadPlayers(); } - public void tick() { - - } - private void loadPlayers() { logger.info(SERVER_MARKER, "Loading player data ..."); File dataDir = new File("players"); From 18ddfa0936494de318f3911628d74c7903ff182f Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Wed, 18 Sep 2024 20:12:35 +0200 Subject: [PATCH 092/176] Perform `isArtificial()` check on spawner switch interaction (#45)) --- .../item/interactions/SwitchInteraction.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java index 3338d653..e3d83cda 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java @@ -110,9 +110,14 @@ private void switchSpawner(Zone zone, MetaBlock metaBlock) { if(metaBlock.hasProperty("eid")) { Entity entity = zone.getEntity(metaBlock.getIntProperty("eid")); - if(entity != null && !entity.isDead()) { - entity.spawnEffect("bomb-teleport", 4); - entity.setHealth(0); + if(entity instanceof Npc) { + Npc npc = (Npc)entity; + + // TODO an isArtificial() check will work well enough as a fix for #45 for now since spawners are the only things that use it + if(!npc.isDead() && npc.isArtificial()) { + npc.spawnEffect("bomb-teleport", 4); + npc.setHealth(0); + } } } From a1d74803a10edd1839f35214c4fa2d1f975df6c6 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:16:54 +0200 Subject: [PATCH 093/176] Make `/teleport` command a bit better --- .../command/admin/TeleportCommand.java | 72 ++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/command/admin/TeleportCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/TeleportCommand.java index 0d1d3dac..c9214f24 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/admin/TeleportCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/TeleportCommand.java @@ -6,40 +6,94 @@ import brainwine.gameserver.command.CommandExecutor; import brainwine.gameserver.command.CommandInfo; import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.Zone; -@CommandInfo(name = "teleport", description = "Teleports you to the specified position.", aliases = "tp") +@CommandInfo(name = "teleport", description = "Teleports you or another player to the specified target position or player.", aliases = "tp") public class TeleportCommand extends Command { @Override public void execute(CommandExecutor executor, String[] args) { - if(args.length != 2) { + if(args.length == 0 || args.length > 3) { executor.notify(String.format("Usage: %s", getUsage(executor)), SYSTEM); return; } Player player = (Player)executor; + Player subject = player; // Player that is being teleported (executor by default) + Zone zone = player.getZone(); int x = 0; int y = 0; - try { - x = Integer.parseInt(args[0]); - y = Integer.parseInt(args[1]); - } catch(NumberFormatException e) { - player.notify("x and y must be numerical.", SYSTEM); + if(args.length == 1) { + // Teleport executor to target player + Player target = zone.getPlayer(args[0]); + + if(target == null) { + player.notify(String.format("Player '%s' not found.", args[0])); + return; + } + + if(subject == target) { + player.notify("You cannot teleport to yourself."); + return; + } + + x = target.getBlockX(); + y = target.getBlockY(); + } else if(args.length == 2) { + // Teleport executor to target position OR teleport subject to player + try { + x = Integer.parseInt(args[0]); + y = Integer.parseInt(args[1]); + } catch(NumberFormatException e) { + // If first 2 params are not numbers then we are probably teleporting a player to another player + subject = zone.getPlayer(args[0]); // Do null check later + Player target = zone.getPlayer(args[1]); + + if(target == null) { + player.notify(String.format("Player '%s' not found.", args[1])); + return; + } + + if(subject == target) { + player.notify("You cannot teleport a player to themselves."); + return; + } + + x = target.getBlockX(); + y = target.getBlockY(); + } + } else if(args.length == 3) { + // Teleport subject to a position + subject = zone.getPlayer(args[0]); // Do null check later + + try { + x = Integer.parseInt(args[1]); + y = Integer.parseInt(args[2]); + } catch(NumberFormatException e) { + player.notify("X and Y must be valid numbers."); + return; + } + } + + // Check if subject is present + if(subject == null) { + player.notify(String.format("Player '%s' not found.", args[0])); // Subject is always first parameter so this should be fine return; } + // Check if coordinates are in bounds if(!player.getZone().areCoordinatesInBounds(x, y)) { player.notify("Cannot teleport out of bounds!", SYSTEM); return; } - player.teleport(x, y); + subject.teleport(x, y); } @Override public String getUsage(CommandExecutor executor) { - return "/teleport "; + return "/teleport [player] "; } @Override From 3e8ca6bda04e0f71c868c8c5b0889f699db88288 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Fri, 20 Sep 2024 21:55:48 +0200 Subject: [PATCH 094/176] Generate rubble inside of decayed structures --- .../src/main/java/brainwine/gameserver/zone/Zone.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 21dba285..86a4e422 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -52,6 +52,7 @@ import brainwine.gameserver.util.MathUtils; import brainwine.gameserver.util.SimplexNoise; import brainwine.gameserver.util.Vector2i; +import brainwine.gameserver.zone.gen.models.RubbleType; /** * TODO Zone class is getting kinda big. I want to split it into more smaller classes to make it more manageable. @@ -745,6 +746,15 @@ public void placePrefab(Prefab prefab, int x, int y, Random random, long seed) { frontMod = random.nextInt(4) + 1; } + // Try to place rubble + if(decay && frontItem.isWhole() && !isBlockOccupied(x + i, y + j - 1, Layer.FRONT) && random.nextDouble() <= 0.2) { + RubbleType[] types = RubbleType.values(); + RubbleType type = types[random.nextInt(types.length)]; + String[] itemIds = type.getItemIds(); + Item item = ItemRegistry.getItem(itemIds[random.nextInt(itemIds.length)]); + updateBlock(x + i, y + j - 1, Layer.FRONT, item); + } + int offset = mirrored ? -(frontItem.getBlockWidth() - 1) : 0; // Clear the block it would normally occupy From f109cef4b4cd79baca8d0dd5741008b2ce591c36 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Wed, 25 Sep 2024 00:11:17 +0200 Subject: [PATCH 095/176] Add zone map render API feature --- .../main/java/brainwine/api/DataFetcher.java | 1 + .../brainwine/api/DefaultDataFetcher.java | 5 + .../main/java/brainwine/api/MapRenderer.java | 115 ++++++++++++++++++ .../java/brainwine/api/PortalService.java | 67 ++++++++++ .../java/brainwine/api/models/ZoneInfo.java | 27 +++- .../java/brainwine/api/util/ImageUtils.java | 33 +++++ api/src/main/resources/map/crossMark.png | Bin 0 -> 14890 bytes .../java/brainwine/DirectDataFetcher.java | 36 ++++-- 8 files changed, 274 insertions(+), 10 deletions(-) create mode 100644 api/src/main/java/brainwine/api/MapRenderer.java create mode 100644 api/src/main/java/brainwine/api/util/ImageUtils.java create mode 100644 api/src/main/resources/map/crossMark.png diff --git a/api/src/main/java/brainwine/api/DataFetcher.java b/api/src/main/java/brainwine/api/DataFetcher.java index 1509e9a7..36385fde 100644 --- a/api/src/main/java/brainwine/api/DataFetcher.java +++ b/api/src/main/java/brainwine/api/DataFetcher.java @@ -12,5 +12,6 @@ public interface DataFetcher { public String fetchPlayerName(String name); public boolean verifyAuthToken(String name, String token); public boolean verifyApiToken(String apiToken); + public ZoneInfo getZoneInfo(String nameOrId); public Collection fetchZoneInfo(); } diff --git a/api/src/main/java/brainwine/api/DefaultDataFetcher.java b/api/src/main/java/brainwine/api/DefaultDataFetcher.java index 8a5b7242..cc738985 100644 --- a/api/src/main/java/brainwine/api/DefaultDataFetcher.java +++ b/api/src/main/java/brainwine/api/DefaultDataFetcher.java @@ -38,6 +38,11 @@ public boolean verifyApiToken(String apiToken) { throw exception; } + @Override + public ZoneInfo getZoneInfo(String nameOrId) { + throw exception; + } + @Override public Collection fetchZoneInfo() { throw exception; diff --git a/api/src/main/java/brainwine/api/MapRenderer.java b/api/src/main/java/brainwine/api/MapRenderer.java new file mode 100644 index 00000000..ba4916c6 --- /dev/null +++ b/api/src/main/java/brainwine/api/MapRenderer.java @@ -0,0 +1,115 @@ +package brainwine.api; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import javax.imageio.ImageIO; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import brainwine.api.models.ZoneInfo; +import brainwine.api.util.ImageUtils; + +/** + * Functions for rendering zone maps. + */ +public class MapRenderer { + + private static final Logger logger = LogManager.getLogger(); + private static final double[] depths = { 0.03, 0.05, 0.08, 0.12, 0.17, 0.26, 0.3 }; + private static final Map colorMap = new HashMap<>(); + private static final BufferedImage mapCrossImage; + + static { + // Set color map data + colorMap.put("plain", new int[] { 0xFFFFFF, 0x5DB830, 0x417431, 0x414E1C, 0x4E441C, 0x2A240C, 0x18150B }); + colorMap.put("arctic", new int[] { 0xFFFFFF, 0x75A49E, 0x456B74, 0x33535F, 0x2D4F5D, 0x142E3F, 0x0B1A25 }); + colorMap.put("hell", new int[] { 0xFFFFFF, 0xBE8D6F, 0x905548, 0x7F3C32, 0x6F3932, 0x5F1814, 0x380E0D }); + colorMap.put("brain", new int[] { 0xFFFFFF, 0xA19599, 0x705C6E, 0x5D4257, 0x4E3E55, 0x3B1C36, 0x2A0B28 }); + colorMap.put("desert", new int[] { 0xECDE93, 0xB18E58, 0x7B5822, 0x614312, 0x4E350C, 0x322209, 0x1E1506 }); + colorMap.put("space", new int[] { 0xFFFFFF, 0xEEEEEE, 0xDDDDDD, 0xCCCCCC, 0xBBBBBB, 0xAAAAAA, 0x999999 }); + + // Load image resources + mapCrossImage = loadImageResource("/map/crossMark.png"); + } + + private static BufferedImage loadImageResource(String name) { + try(InputStream inputStream = MapRenderer.class.getResourceAsStream(name)) { + return ImageIO.read(inputStream); + } catch(Exception e) { + logger.error("Failed to load image resource '{}'", name, e); + } + + return null; + } + + /** + * Creates a surface map render that is visually almost identical to V2 client map renders. + */ + public static BufferedImage drawSurfaceMap(ZoneInfo zone) { + BufferedImage image = ImageUtils.createImage(300000, zone.getWidth(), zone.getHeight()); + Graphics2D g2d = image.createGraphics(); + int[] surfaceArray = zone.getSurface(); + int[] colors = colorMap.getOrDefault(zone.getBiome(), colorMap.get("plain")); + int[] raster = ((DataBufferInt)image.getRaster().getDataBuffer()).getData(); + int surfaceMin = zone.getHeight(); + int surfaceMax = 0; + + // Find highest & lowest surface points + for(int surface : surfaceArray) { + surfaceMin = Math.min(surfaceMin, surface); + surfaceMax = Math.max(surfaceMax, surface); + } + + int surfaceCenter = (int)Math.floor((surfaceMax - surfaceMin) * 0.5 + surfaceMin); + double scaleX = (double)image.getWidth() / zone.getWidth(); + double scaleY = (double)image.getHeight() / zone.getHeight(); + + // Fill raster with background color fast + int length = (int)(surfaceMax * scaleY) * image.getWidth(); + raster[0] = 0x80808080; + + for(int i = 1; i < length; i += i) { + System.arraycopy(raster, 0, raster, i, length - i < i ? length - i : i); + } + + // Draw zone surface + // TODO potentially slow (largely because of Java2D) and can probably be optimized further + for(int i = 0; i < image.getWidth(); i++) { + int surface = surfaceArray[(int)(i / scaleX)]; + int distanceToPeak = surface - surfaceMin; + double y = (zone.getHeight() - surface) * scaleY; + double layerHeight = 1.0; + int start = image.getHeight() - (int)(y * layerHeight); + + for(int j = 0; j < depths.length; j++) { + double scale = (double)distanceToPeak / zone.getHeight() / depths.length * (j < 2 ? 2.0 : 0.5); + layerHeight -= (depths[j] - scale); + int end = j + 1 >= depths.length ? image.getHeight() : image.getHeight() - (int)(y * layerHeight); + g2d.setColor(new Color(colors[j > 0 || surface < surfaceCenter ? j : 1])); // Only use snow color if surface is above surface center + g2d.fillRect(i, start, 1, end - start); + start = end; + } + } + + g2d.dispose(); + return image; + } + + /** + * Draws a red cross mark on an image at the given zone coordinates. + */ + public static void drawCrossMark(ZoneInfo zone, int x, int y, BufferedImage mapImage) { + double scaleX = (double)mapImage.getWidth() / zone.getWidth(); + double scaleY = (double)mapImage.getHeight() / zone.getHeight(); + Graphics2D g2d = mapImage.createGraphics(); + g2d.drawImage(mapCrossImage, (int)(x * scaleX) - 24, (int)(y * scaleY) - 25, 50, 60, null); + g2d.dispose(); + } +} diff --git a/api/src/main/java/brainwine/api/PortalService.java b/api/src/main/java/brainwine/api/PortalService.java index 55bea25a..42698294 100644 --- a/api/src/main/java/brainwine/api/PortalService.java +++ b/api/src/main/java/brainwine/api/PortalService.java @@ -4,14 +4,22 @@ import static brainwine.api.util.ContextUtils.handleQueryParam; import static brainwine.shared.LogMarkers.SERVER_MARKER; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.HashMap; import java.util.List; +import java.util.Map; + +import javax.imageio.ImageIO; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import brainwine.api.models.ZoneInfo; +import brainwine.api.util.ImageUtils; import brainwine.shared.JsonHelper; import io.javalin.Javalin; +import io.javalin.http.ContentType; import io.javalin.http.Context; import io.javalin.plugin.json.JavalinJackson; @@ -22,6 +30,7 @@ public class PortalService { private static final int zoneSearchPageSize = 6; private static final Logger logger = LogManager.getLogger(); + private final Map surfaceMapCache = new HashMap<>(); private final DataFetcher dataFetcher; private final Javalin portal; @@ -30,6 +39,7 @@ public PortalService(Api api, int port) { logger.info(SERVER_MARKER, "Starting PortalService @ port {} ...", port); portal = Javalin.create(config -> config.jsonMapper(new JavalinJackson(JsonHelper.MAPPER))) .exception(Exception.class, this::handleException) + .get("/v1/map", this::handleMapRequest) .get("/v1/worlds", this::handleZoneSearch) .start(port); } @@ -42,6 +52,63 @@ private void handleException(Exception exception, Context ctx) { error(ctx, "%s", exception); } + /** + * Handler function for map render requests. + * TODO throttle + */ + private void handleMapRequest(Context ctx) throws IOException { + String apiToken = ctx.queryParam("api_token"); + + if(apiToken == null || !dataFetcher.verifyApiToken(apiToken)) { + error(ctx, "A valid api token is required for this request."); + return; + } + + String nameOrId = ctx.queryParam("zone"); + + if(nameOrId == null) { + error(ctx, "Zone not specified."); + return; + } + + ZoneInfo zone = dataFetcher.getZoneInfo(nameOrId); + + if(zone == null) { + error(ctx, "Zone not found."); + return; + } + + // TODO proper cache management + if(surfaceMapCache.size() > 50) { + surfaceMapCache.clear(); + } + + BufferedImage image = surfaceMapCache.computeIfAbsent(zone.getName(), x -> MapRenderer.drawSurfaceMap(zone)); + String position = ctx.queryParam("pos"); + + if(position != null) { + String[] segments = position.split(",", 2); + + if(segments.length != 2) { + error(ctx, "Position must be formatted as x,y"); + return; + } + + try { + int x = Integer.parseInt(segments[0]); + int y = Integer.parseInt(segments[1]); + image = ImageUtils.copyImage(image); // Copying is important: we do not want to draw to cached images! + MapRenderer.drawCrossMark(zone, x, y, image); + } catch(NumberFormatException e) { + error(ctx, "Coordinates must be valid numbers."); + return; + } + } + + ctx.contentType(ContentType.IMAGE_PNG); + ImageIO.write(image, "png", ctx.res.getOutputStream()); + } + /** * Handler function for zone search requests. * TODO could use some work. diff --git a/api/src/main/java/brainwine/api/models/ZoneInfo.java b/api/src/main/java/brainwine/api/models/ZoneInfo.java index 9c6e2cb0..5ee4daf0 100644 --- a/api/src/main/java/brainwine/api/models/ZoneInfo.java +++ b/api/src/main/java/brainwine/api/models/ZoneInfo.java @@ -2,8 +2,12 @@ import java.time.OffsetDateTime; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +/** + * TODO split model in two: one for internal use & one for {@code /v1/worlds} serialization. + */ public class ZoneInfo { private final String name; @@ -13,11 +17,14 @@ public class ZoneInfo { private final boolean premium; private final boolean locked; private final int playerCount; + private final int width; + private final int height; + private final int[] surface; private final double explorationProgress; private final OffsetDateTime creationDate; public ZoneInfo(String name, String biome, String activity, boolean pvp, boolean premium, boolean locked, - int playerCount, double explorationProgress, OffsetDateTime creationDate) { + int playerCount, int width, int height, int[] surface, double explorationProgress, OffsetDateTime creationDate) { this.name = name; this.biome = biome; this.activity = activity; @@ -25,6 +32,9 @@ public ZoneInfo(String name, String biome, String activity, boolean pvp, boolean this.premium = premium; this.locked = locked; this.playerCount = playerCount; + this.width = width; + this.height = height; + this.surface = surface; this.explorationProgress = explorationProgress; this.creationDate = creationDate; } @@ -59,6 +69,21 @@ public int getPlayerCount() { return playerCount; } + @JsonIgnore + public int getWidth() { + return width; + } + + @JsonIgnore + public int getHeight() { + return height; + } + + @JsonIgnore + public int[] getSurface() { + return surface; + } + @JsonProperty("explored") public double getExplorationProgress() { return explorationProgress; diff --git a/api/src/main/java/brainwine/api/util/ImageUtils.java b/api/src/main/java/brainwine/api/util/ImageUtils.java new file mode 100644 index 00000000..fe18f9b5 --- /dev/null +++ b/api/src/main/java/brainwine/api/util/ImageUtils.java @@ -0,0 +1,33 @@ +package brainwine.api.util; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; + +public class ImageUtils { + + /** + * Creates an image with a pixel count as close to the desired pixel count as possible + * while also retaining the same aspect ratio. + */ + public static BufferedImage createImage(int pixelCount, double scaleX, double scaleY) { + double factor = Math.sqrt(scaleX * scaleY / pixelCount); + int width = (int)Math.round(scaleX / factor); + int height = (int)Math.round(scaleY / factor); + return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + } + + /** + * Fast image copying function. + */ + public static BufferedImage copyImage(BufferedImage image) { + if(image.getType() != BufferedImage.TYPE_INT_ARGB) { + throw new IllegalArgumentException("Image type must be TYPE_INT_ARGB"); + } + + BufferedImage copy = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); + int[] src = ((DataBufferInt)image.getRaster().getDataBuffer()).getData(); + int[] dst = ((DataBufferInt)copy.getRaster().getDataBuffer()).getData(); + System.arraycopy(src, 0, dst, 0, dst.length); + return copy; + } +} diff --git a/api/src/main/resources/map/crossMark.png b/api/src/main/resources/map/crossMark.png new file mode 100644 index 0000000000000000000000000000000000000000..52f4633d56df965dcba89d95f7fc097303565668 GIT binary patch literal 14890 zcmV+_I@QIAP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>DIloCnK~#8Ny`5>0 zWZ6~U@5}dcuYKvN>gv6Fww^^Zn$buC%UaL^NPvtj2N(zZ zWF}vav=kS1I}j7Pfa@bXATkVxR=VH5{*6_6PaOAPrMJ}LX3C|uQ!d;Lt?d{Y)EL|& zRM|i`!cNBpy`DR_wH3TFKOaZku8Wn{ZZs_4qqSPFm&?ViY}Pfz)sYd`PN!Wfld(Gb z9XJSsBiF;>YP9Rt!h05a3jfu@Syz4j1=p#Eu2qg)s}j2QeyD2|%sJPoq+CxO3Uk(+ zdZKhdRfVq)>M-~39gx2`lxSov=^v|KfI79G+7g)S;$6wIkvbSB-L@K#CzW#DC~_Tf zOWf7Y2>BUKrLdudg?+&(G&?Y;VWKVzHN3nV$0Oc~AwgwQU_Z2ww|SXtbAC zoc{w?Ni{4j_}ZmQ6251nj9L%|sh*399hb_eS7GeBO?9RyEhvbyfy>WIgSrB}o>CaN z^axe;dueSQV$xNQX{w1W;>K{;C!YgROjEmxNX}qWyd!W+$}f-ylynSjB@Rdc+7v)- zUAMz<+O^yNRkfT@wm%ZjDQ7ty1b-Zr%caLlrQnst#kiZxxq6}ShCm&rVa*M`Dqsy% z5AIM1+jLYRQ^-glPQ)(K0HqWM)ULpd@&Y`q0TFm;b~GX$R~_F0sF3B-KmBR1$hH#h zDQ89PZ7M&4+=r;mbEQ~xwTX#hV}Jigb@^|V)>6egs&P-(>UHqhC=8#gG@I3#RI1UE z##+@!K>JQW_2j_~e{O2Z5-h8FMQB=UJ*2Dsbnf&t<t4Z7W_?>ZY#u6yFtSyIR0{aXOu^Hk(=vWzr~@@EZly)5*@HV}kbq zxlbWd2ilu-0^(oKkPoRRr971B3ZTxC7R7ICVYnW-_TA8R%SYOwU`x0P$nKIlxUNB7 zR;blKW?>}H>vWJj2CM)o=kxB%r%q+#Uhlk;w8Y0-0%}X)EnTCnThh**RIgXO3|u_u6KLge>Yxt>7mmBiBY7~nfro0#ArwAj$K1@>=Wwn*(Y`YzKc0rf%2xv1n# z$+fHdJL=E2v~yDnGR7JhUS~B6#b#pCp_1hU&eMu|vQrgvSq+N4EF5aqaxJ-eqnaXFZI(=wuC83wi z3QQQJrd@qi8YUmLqiL^wzyYY9RrhdsNIq<% zm}#6*TWIXxoIgLSPQ6$1EvoIhipbEovQ^#d)g)_OVY?%sXLWC?Ty|sCsw?d8E8Ck# z17IP#e@Jk#*NA}@CQM$4e8+(r5-RO}pbnSs1~LQ6?ZLz`IK<>hLIYSlkW-*`LutHx z(Z$`}AWWq$D*Z_b9;rxL?T6}viUw3ce;OTWtE<=Rk6yXbnGm2Swzk~Fy?btK4{z+v zp+mqzbl8OV+MxIV*hB+y-Ysu(?>JD6l6>4hj<^d0%s?{}_Ml3$`mFl^>q8GNgHL(w zuH3gTUrwv+W6FL?a^!S3t307t{|EIT43!+Wgm_aXpmDQM=mBO{F51lYwwv7DHGtm? z5+3SWA|Pa)RosKibPJW17Fag5#oVWJQiVR~Vh=ib`f&E?exMFWb=Y9h9^nvcVd=B7 z0dAX6`8*>7ZDrbR*O8p}x?OzLN5vD8F{8TCSkqKV*OcrXc?<1kyIsAxw$=<(ZdZen zR)`NgwX2#nVc+4k(V%inMi1DXyvK0tp4+gx?9M z-1h@XyQYPNN7w^w;3}m3mh$4a8o-kAp{1piX3A*+c~)EiP}R+HA8XLNy2f9vxo)?e zZ?~Htlf(w7N#FALa`uA2{U zZKX7I;<6f-@8==`ngQHq)3g;(0Sfk73^8B^+!2P%+z?lt464_9czA#YgRKx*VKWsf z(-WxFu{}4JkyG)k^8S$898><9YHq9Eb;VZ{rZqiRyXo{!u~@8@Yqjoq0ZU!XNXTZM zIA3SY>r+F1@f)G=U#sBC+FB@JPpg5Sl#oBD2HvU$epdW$<;gcvspXZCk(`bbN>rDek zefz0YQ8&L&wY^{UMFJO4YbtYB@l~rg&_+3mHm<+)QazW+#M;Wb83t3-m+BzQ%LViF zM!*e!L0UI=`gEq=>n*6^$Hb2){SgTxZ^X?>*e6n{)FY`d%uh{E?`Xt#!?7`!tyEkn zKvT*2CgLfd*HvG@^9|K;Ph})(|>_ zoH$QfR>h1kaT`hlIF3Ief&aZE{o(GyLX?r;$3b#d8f%@@r~{e@ZeRuy5Ig}KnhMCK zu}QiOfXK>=)uvq!LeDCXErioDJuq#pudarjbow!A^TTQ{ue^yG2JGQlgaUoP%QiNh zZcVG6)5W5@sq&l3c9*OPAEh^HbHo2us|C0z8k@AHA}i^rgp&feqT&dt{sp1Jg3{Ab zrLv^>?J@a(W>;mW>KrPI(*lCk!fUJvsx*Kk?hl_?5J|V&Lkt1FnFN17DQj^bCIA-! zR&^EU=T9i#59!wXQ~>{#G22soOT47|)^)!kaPL0*;)`Xru%;NwbW3#*G7@9r~&e#|vf0YE|vDcsb3K-FFnwE8UK-+oMC0+?w!*OX1l`#g}>bUd0TE0xsh?Cd4U z^v{&%VKKLneNSoYs$g9(#+SsU!kB`S`0mq^RsGcbS zNPwyixu!I>UrWLw`!~r)9AN42ApIC-u*^=Y&gUA9)Y#f@+z zLwr~9jkw!glY6yUtJm9ow9_i z$lXHec$&rbD<|Rd3Q42WGvcSz$;I)hsa*tDemj%G-_1*t016+G(Nb$?eb_vX)>sr2ggdlE_uuIy_ZM~bH< zC??cDx6MnShjss1*||W|@|t!H^@2c+Byd3*QB%j!EI=K21;d;WF}qTlX)GjwjfV0Y zxT+7&BQM*?EYXJHqz3gzmE&2Jh?EInwSNe<72i+|x3s7&x1*>eIrl2%va6~*ZW}bN z5D;ng%>tL_t>G^!g1eGd<{hQIUDtoLvC*B%=l7*)JJJr@Xo)3jAQP%RINQa30_k(& zcS-1z%DcTaI=cJB(o%aAkRendAO(mh-(WRiPpXRzz4=eA7LN+Xk5k-yEeFQ<)=J^b@FQJ)1Wxs#9JlETeI3eE=^AE z7F#WilX@x5;()|^0sup3oDgYT83U^1F<=wz1zcjr43O%g%QY& zQhaiIyRmiZR7s39rM!YcL?p7dOlx%bY*4vt`@e&(4uu; zU}55kreYHOLdLYUGXtfwDe$|kXn%i9GQU@a-=kad_XEt7f`IL8M>Ulczp4Scw!E~o zB`+lgSZ@Xh{~^FFjkH|&pOWbXvnVj_c#&a3wvspt$ezhj&|Afm|3x zyom8J6}xP*^k)%yQ2?G3Z_56@AoYAH3d72tc8kD$+0lU&`wf4g> z?D)a690_?~Ni!f7HE;nB%~jlMuxYRIt(8jQ*v!n+O8Bs{pX*nng{!}nt?R0)@UGx{ zK58_sXrXJfX{pJ+9!GBmxIG!6rKz@CnHG*7SXNs?fTiQGwtx1z4r$v{QyklMOJ~n^ zJKNi(Ogg=!PU0uxF<{?D=@`18v_4K+bd}Dtr*o>mELUt(Hj)QS+#V3^T9`1}1i*lb zhXEkA@et=Um8FGlYHn^^9tSSghsDf6I)cqth5%o@sk)XlC|?auoVYnBZ^v(3_NDR1 z(RT}OSL^(>#k9LK8@lk91ssA(7z;h9oN=-W`*)#ekVRv5b+z~S+FEx}{_fV;*s}60 zQKxuZJf(WfEGU0p#vsTFs7neT6R2^NO5K!jHM=LEXcq!pV*M+H39z<3)-|k!(R{rJ zDwW{&#KgSHd`Oqih*`AgH^dsivn^b%CDrrS&s@23x0lJpQuM2JJy)wgPICZd+W$U4X}oa6YgmSFVxEJ)m+Q zQThe(uqi@g(N+oSUeHYW>vjnz!E3Jar8#O-JDFj7cLas^;7)?tPkuk zsDvUq3z#+?HPUHuZpc1ypOtxGeLX%^E|)iEXKx6!HQiz{Gwo4Lm6-KbivU|>a|*ZY`oh9Wy4$^>I?&#pLhN2fxtYPHmKG0!2NXW0O+!_SlRj&7`;qOxsj#E|4PT**hW5Rt^fUEU|+c$51^JEajm=Ofv zF*)SHh29Kszj>)BQ*hwRhQBNgunwsc7K&{JPQXMM;z!93fM?2`E0x;ig@vtJ5L^#B zoqOt-aM8l1MN`ZOrc{c>jg@ji@h2EG#cwHXPkC*+QuO1rDU`HK(j?k>yai!-VRt)*_-Bm`V*OZ44<`|_E*yL~S2 zs(D*pjKW_5*rXvsK=gK1!u2aP*%LDXCuzgXJ(UkmluB{w+_`$(YAtCo++p{mdlSk9 zu-IVJToKckR~;V{AVpo=&^0ftnTHZpo2@CHTI*yaGQ<8S26e7@amR;F6o2`RyY3JnYJ z-GEz`w%!tVhO`w5|3fCtd!qe92%UtUxn>#yp6}pMCLb=D_agR2N8RO@Uv}Hm(=N>A zcD0{hQF-j{h`NqTb{Je1yC8T_H7&Hd-HmjiQ0g_CZ5~pxr}tV*J4(n0QS^Qd)CYA< z|ImEiz{N0Q^l`Iza`3s8YuCOo7DX}ITF^kzpy#9XM!~&v5;IUx>~O8&4`fsB6t8O| zG{6lpWT#S!8@R_XWdN1M1z=&`Kd6q5#%Gt7dM`ixa4)sBRh5H(NBJwtKP6C3s{X!R zJfO6|xukHh-EMDBgyHQ?|w`YT<%4GDH%V$JXa{?@X=N+gVv% zy*ghka!zT_)0VZ3otm0@TJrsKNq4ps}Q>dQ3>MUY8v@={lWGuXg5)%dD^Wv`ywY6vvQ{C_kRTQAOfR6^(o> z6^8fLMn^Z&QB-YqyX@XD18-3|UU)NY75AUz5Yr+pPvp;5Ros{E8nzuE)_ z!L06GmaElnM$sMF%`?jXPbBUGcDHX|8AG~*=JFWnmfW@9O|{#1sXqZ|(H+ z-IwSGeIO5K$g=0IEglh|Sph4VT|S-B4CQz*<#xwXZciS%l`~30fgH}s$zv(y*AQtX zu*tp+-`5Q~|mtFqxGs#Qj#p7^tL00B#G^eHrjN&ru-m zC1E%#RR+{hthL51YjK@Ug~nsz0jOetBOC@4-~o_0;M!nFGud&Wp`10O#Jmo~`9oI1 zGR0CG8@=^gx4JV^QyQ)ArWU*t>R6b1;z6qsP;HLvn_Hzi)F6r z)^F!VM_*JuJ=7U60e5F=>JYdsfyjXlN`hwcM7gGGOaK7WXYv|Yu*He;kGdydi!JAN zMgmu1!%jMsgy9LfV4)BJTm~dk`A~dMY79^(13CT*@gWHhkt)G?r)U1a1Q0oE;PoR8 zsX1@37dRAh0K#ENRts8oZ_k~}<>Jemo9*@K=~SG_tgD(Es*v%YSDYPLB5j}4DUKg) z;)%WfFv0pJA&y^^Y58Nxbklwbqr0?^M)KD7mdrw4pt884iG($P6juc%ChpLiI-V1N z0hLQyj|9d&Nm$V)y^N$Lz(E+!OUx9&Dvde;wIY33m*&<5Sx%n937Imq@(@vpPfI}M zIh8+V^sgHEklmeus*dJm5=JHLwBq{$_x$E&>-O|?C@6L{fY((Yt358)u;(*O2C7;I zR0bAlnhO6`3+BgVuF954fMo^xhB*P%hiC>~fPVuJKvkVR zP9X$X($HF*eU2=!VdSso^X|d5wRUN7G0 zoCfL>VXM`m-VwQ31!ji^GTUg_Opyy_GW%^Z3@F2FwGo$m7Ei!c%m~k_3sx$;n`o_h zTjYflhV#mxf$f_B5EvrzX)9w-ZBJ_kT3J{^d4>R#GXk4-$+ZtX$Xqjr*$&!sHG~#1 z{rIibzFeAf%gf!>Y&O;=qbZl^s(Q~OY~I!7fa<=ax+O|n)jxmGZnd5-b~>F&eqtmr z0oL-MsdR|8C67s-gvSAkd=~eh5*OLO5*_1SmaAILX#o>=(M~AI$6Ju5rN@B^-I1l; z(YSJH3`m}JJpfgL)40J!NCk;dembF{#Lc@PRHLM^A~RJ>BO|@1*VgLkMx)Y+TBB14~(YX*cZ@2TW{ z2-1Bf;x@u?0xBWw!6h7Kv~`&MW`pkx@E$`$KBOG;b{4bH>lL-&Jf-`*qlL#{&A*EJ zX9vCIiHU0uluD(F>^8HA)gKucM3YgO{EX`Z(U+gTn18MinV4ZbwTAp@R{u&R$gc9X zIS^N6V;d~D1JRPaR1T@^t6Ax{We!SmwoCxgA&?~SFay#sd(AwK(N@z~F$)-eWsTQ# zbkE6Ms`q-WPOmp1`DWE}PT=7I9!rvq7p6Zf5dPg)E?&GmRjmd*#=-9MwxbQ8m>vKM z)bC9{#7iKR(1>dWAi4H=Azyy1-XEZShI>H7wT_(9&cS>aF`;L!txhr3OvpQUhF1h6 z8~_6B#q6(lae=F zote4&;Ogqesx}7Jh8R~6W=$ZoH`=&d$b{R~2O`l_g{G;nr5rTObu%5?;#JQ`b2(ie z5-U6-zXu>$z^KpHO&*Lhi~uCDYg$aD2cRO12llw@`?1eQXsr6^JY;#)aRxKBy`Aeu z(L2@l`*q1X!aU)%aVPJ6h(13mP3o;pPhXSW-eo43f33m7juWSTDzUA%+GHzkZ36)J z6b?WYlh-%ORV+uZtp%(d#MPP<;(F450oO+1qhX8}`sW9`+xa zao>lYAs-(s6tBUaXwqwu-BU+!~VyDAoFHNDL(8q_VPGju{vnK5yPI+Vbwn>hrZDFKgVQ)pH+E&2_x*S#-vi~ohpub+k8Xev5%-! z;-U)tn?@LZzudVo&M@$8?JaZFhVoFMRi-Na0)}Z~q_{UcV%#%QUbcLZ0GqBzF%(v9 zB9o)fwoDqB>M`Io)Y@^}6h}foq=N3h!1&e_W+_--{zc z&R>~Wz~L7#&R>5+?%AI!KCU?DxqbjVu~FjH{`&g#^i7$9T|ngUn;jWuXkT6lT8ihu zIR;y9Dxm?BH<2BgiP?ZClInFd5Sn$VuB&PVz#OosaW9;ZDcF~HSO-}3au`@l0-zGo zrFUdoftMy74MO#Yei*SSUT-_tTYR60lX=(LNCzo5vHX60Gc4qv-XIt$7 zbOW%(fXc({q@8|gv4IO4sIt2%o9c1K+%Z?HJd%-< zUsm#noNqIjeN{`gXu-%SQrmWIplbTFCIBL~wxt%jQc_$mPS~~28=CTp4}%G_pux{b zTc6Q>{+xiNXE0=74TneAguEXoLNom%{8(L2&dn_fpr6+5@8Mc`SQebHV)di(nP{5`k7-`bT) z$g}-b`@9%po{2!UQR%mD+UnWNhuD52K|aOp^Mqo&0yMTRpOz!V0fgF|j!#H2nyBf&FL`M{xYdS`eI6_%3yh^8rBM0iy?2ximZbe(Uqf_dniw`}R#2|xFk&#rhR--Q7TGVWe51r)-7=Dk&4?y^qVQp-DyrgaGeujSGkw_+P93G8!`jiBq_p2|wA+8`qNvT!rugj^ z?~7f$bEmU&?p&(f>+vDpHEA0UNFG)hnp49ffva>r%~uG+a6dmfdbizdwsH6P;Rri3 zz*Sy*brj+-iNQw!?cL#oBNi`!7SpMY<^-moG$E5hCjsy{;vNS5-2(NeM(iW8mPVrk zQhey@;NFptf8l-)MTsUrmo_(pJ9Beqb@i;e&QCWlN~TK+#|#VQ);%)w70tg4!tggw z?(MB)1uEVIi%BHMzg#G|OUuih&6$~uYqzTcu%vb#Qo(6uGbI$^A+VUSGb?tQ%~HMB z+sw(!091ZpZ9pNPh^M*c_X=F>>%hh)n$6|__<2f_)#9|D=hRP1)9>2x#Jves`av1@ z$JM|9Ea-l5$X_1`12Yc8Tb25h($A~IycjpGxV=f(&n>0F6Nuyg z_4XS#J~^o=kCV@oY{7J1pe#$)Yin!G(W$At?B=raN2>2Z)qtPN?uNw=VEIk(1T|+; zsTF}$Vxa7ke7cFLlFjZPeKJ!48~GDpxlbHG(}1gxcMX_UEMl(-EZX{yFaw#4yn&&g z#d}KPuS$pfHKCY1mJfl%bOQTTIdLN0s@E^d);*=1rjk_ z{-xdBs*G=MR&`Jx_3&bd`J?LWg6yu+)LWU^Fx*uh28qoBZHzutwsuP(_?j*z%@FW;e?dh|a_R&~et=9zhT^g8Od-}8(*ctUx{74q1-{|06p-_^a} zkrcoG(v2I9d=%MS22`dh9tAd$G+s(!@!{R8i@Uqs8w(3O{w_;<@8}kb8L~*_v^H?C z)BjA%yyDE7szBwXgeU5IP08Ge(It(rq5z?XKz$Hv-a+mJjHM=SyAJXLm zlK5%W^@#FJD70;-()$YT+>*-wuBP@M?kp}gi+g*$*+#>))D{|hMw&@<9ZzTuUY3Avrg zWKx~BP^8bQ&weU}k|+e&9N?-#Bi(064*~nrTBv?dTKb^0w*LU;FpwS`A0OOfBvl~| zlj0XC;-s4fp;ZcRojf_Ee1LjULOibUNilz)fH$<6mL`+R^2+-s)W&bzynTE9!*}lV z_(m*mFwUw&9G(E~lCCGERm5r2&UwWa>K^ZHy`ui)M@Hg}t5*ZSEv&ECBn!WV;C0}# zLf-B4AGv@y_~G@Yw@6Lj*J-sPfmsMLnSE($oqo3PY80ZiBg`7f#O$HJ)G+||C#0!> z`iz7ik(&cn1<-4Oi*IL@>e_(y9rdh6tNHw>2H_zI#>qZAEPlDo`>uQ;$(mI<)$+@i zFG}#=h^D5#@&2{7?rs>ylXa9!TBr=OY6lk!KbsDr2uxGYe!5oZthlCladb2;z4A&B zJ^geq+}^IonT&SPL0j5cld3ip2W);e#gPe*(@&@}UWon?$@6xBdtUKHP5WAq%Y`fJ z>y7D&iTHe_64+vg3~CDsKcp%D5hea(g>Mt6DrlNUrb8@2p#iwccNARgs0WuRX{>ba zrdq8+DvBP|;=rzo?JAprODfMxkZfCRkQL{x!RIvqzm*#u{d9SJypwLVdMEbwY-%(0 zXp66O#Vm6Cjf7KL(4M|}HU1l!l~eQc_5FH%Pm3O!%J~5YdyE$! zxnOewxcrEV{?01?HUa&#!277KADEb!D0ariR_jqzn=hAxs%8|vEfoGoUA#|#;!*)B z$sXMPD%S_jLa}c0uaQrK*@-~xT<8$jX79vOgFU<-9^l!Smtj1Y}2 z3IJRx>cBQd+3W@Rh?fmixmU{jgv#C0XU;_7iII#NF!}QP#*rO z2JZ>jXQmWVj89N~R^e|>2En)aLg>C+q#60FXd{oUt$x}OL&!thm9=wly|pukIIjcY9ARFE_6}{&*$W+S*dxTWV)X^)D;DrnYa3R|G2G zOJti|)-~UprU}-IiyHJ-9$a64nYSl{FKN4qKnP8vXc-|4q0MvvVay3mI>iD(XXrE` z>DVxPPfBK`G(J8iZ9S*ayr8^nB`>JZTNIvE4HJrEzYq#S(9ft7zmqE#Kl7FsUZ`t3 z+2g|#_JB{Ia?0dEMRPs4NLf*yeK7!XJ=n7*p_P-I+#?Sq33)??zof*gO?)h8XJW#B zNLW#totTY}r`_%@Hg;T|MoxX1k&JAO`9(foRi0Jd9BtCT?}fG6UoKXwwRyg+`!7`~ zL}UF))(=f=HDbqdfTz#|SKLp1rX?c}xcn#r5H4(Q2j8BVnT(={74$(hjIYY?vv9Ha zGbLtM#f+7b<88%1C5`^L+I_B&O4Ty0R)@E-{eaO1;6k+P88U4ZanTq`9UCFbh{_bgT_Y_`J z=C5ih{Izyq-;UFfJ-?BkUIE18XrPOLHOBJPwm&GeWSuC)TSvd zuADd#xl$=NvA-W_X2#m#Y02&8-*M|!X-%_ifmx=yQh6V0*?dwlBDg>0X7n z8f7oYnMH!yi|R0+mlE^?D? zisPP)D$b{(c+mHf_;Z>KUr{^Hjbt*Lk6yXb2GpE7!_>!)nhXgIVv?0OfFh5X0(F{` zGtOT<0pdXZga(?LD2DY{X@JU-UuOWAiE6tmV3`3~`lqOxCT)D?eLRzVXf1HCR)S|4Y)=zfk(~0_5dptF`@}J9p63o(C112N4=T!{qOY z4H)HT>cx`BKck||0N8{KQ~*str4|EKp@B+X^$q438YYl!GOw-zU`D$>wy(Kr)v#gJ z#h&vmFRM#T2MLiPcxjZ+WF;D#fJ;G~Z!-g`*iR?+$Bg3M-d=d}p@#}VvpJkonEdlF4o@j#V>Z{R>ing-UB#}4uN|l52=9FisTvP zMpREIKj`3hAO@=H9tIU9GH;(gdQhnsP!)0vm?P9Zeiz1f7XX*G3{-Jm>?QHx!B8rG zI|0^MnJtk*V$L7S0`z9J8eUjf$jV5Mb))EHBzq}Q7}V1e>Vit4nden*Q2=nJ%6kEv ztMW?i3rfdF{fe~xTb+7+t$FccdwzAbb7Fhj{=Um`ntGH>rX+rd07my-DV%dxiznSk zR92^3?xE(^0jLSElm}D*#n1ykpjz8v169}T(9l+U+eM(-kM4BsK?V3>S$v$qZ)&@< zJSy9}JPfSe)~pxvcmpk3s8&;dck$w)?ma4`?c?I9FqD-rJAGQ$CnXsB|2f6^5eCnY z_(EJw>7SO*_$k?}Z}p<+rX2F}d#+rmT|0NK*U@4yQ!2TMa=HHw)a#KtL+4cQtLS$p zf7pGo@K%=%%d&@MHyv!a$++S!bT%|)+U`uNZh&%dvn@pdP)rYi!Yq`BKo$EBy4fQb z<+tsz7;uluzymo<5}XSJ8R!hnDhJiP(+s1vkkU^OMcLP`iHxS7;#*Ur@4+2E$jg?kdH z;^w;YMY|l}u$J_{LCaQ|&qkfmN>9xk~ic@N03}V>|307#@|Z33 zMVA8@^C;!>TIIeGbh|IeZCPGfS!wath$^F_F&m0~X)6m2nhL0F23}WdB_H&MXFllu za_a4FHCS-pOg-j0O>Lq1BftVKB@ol$mdj=~j5C>SmyY(dw$^p8tyQ~ghcEbiU4A)}5$AjE`gBI7R2si^UfXy6q#e_d&Itf`tknvhd+4E&sM)-r z{q-YKN1pv-(|N&xC#oDq@X{K;y1t_Db6V`a6^7wWnU1@yZg)?PbAvO~yrviKj{OZV zh4_)!Z@@%TdApjB>E}(tC(pjqZRIE2%E$?Kb^NS*dE&fgmNo(%Iry!Nzy#QWYt>{$ z8yVBupx1CwYR7=h=avQRs%dQ$)dVav;`o6(N=E@FKVrWyiPy3o}=zAa!|S=Xu;T(edbxCL!L(b|r-wq=*g ztZFNITYN|0ZmL!hr-j~1Rdbe)5czxYD zX%{;OKIO4ktEJcj&o3-=wV35onG8R+;LlU>H4M%pFUo-MeCn>iTF+FgyHBsJHP&Zl zdTe*6?%i|ynl^xlhN^eA?esgO&o4afb~B@HH9zBSj4isW6K5>_SOQoB*K4hSmBu#r zM_jWyB9QZ@wU`4mwph6Cip6U#m%A%qt7^J$>i!|rqJSI!2?Q7W~`A1K$>v{^0`VipE~ zL=&^(Ymn9#| fetchZoneInfo() { List zoneInfo = new ArrayList<>(); Collection zones = zoneManager.getZones(); for(Zone zone : zones) { - zoneInfo.add(new ZoneInfo(zone.getName(), - zone.getBiome().getId(), - null, - false, - false, - false, - zone.getPlayers().size(), - zone.getExplorationProgress(), - zone.getCreationDate())); + zoneInfo.add(createZoneInfo(zone)); } return zoneInfo; } + + private static ZoneInfo createZoneInfo(Zone zone) { + return new ZoneInfo(zone.getName(), + zone.getBiome().getId(), + null, + false, + false, + false, + zone.getPlayers().size(), + zone.getWidth(), + zone.getHeight(), + zone.getSurface(), + zone.getExplorationProgress(), + zone.getCreationDate()); + } } From 630e0be490abbea754ef60f101703640631988a0 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Mon, 21 Oct 2024 23:56:04 +0200 Subject: [PATCH 096/176] Incomplete fix for zone save bottleneck (#56) --- .../src/main/java/brainwine/gameserver/zone/Zone.java | 10 ++++++++++ .../java/brainwine/gameserver/zone/ZoneManager.java | 5 ++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 86a4e422..5140dc88 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -97,6 +97,7 @@ public class Zone { private final Map damageFieldBlocks = new HashMap<>(); private long lastStatusUpdate = System.currentTimeMillis(); private int ticksElapsed; + private boolean modified; protected Zone(String documentId, ZoneConfigFile config, ZoneDataFile data) { this(documentId, config.getName(), config.getBiome(), config.getWidth(), config.getHeight()); @@ -992,6 +993,7 @@ public void updateBlock(int x, int y, Layer layer, Item item, int mod, Player ow Chunk chunk = getChunk(x, y); chunk.getBlock(x, y).updateLayer(layer, item, mod, owner == null ? 0 : owner.getBlockHash()); // TODO owner hash should get updated on place only!! chunk.setModified(true); + modified = true; // TODO this alone is NOT sufficient! // Queue block update if there are players in this zone. // TODO maybe check if the block update was in an active chunk, too? @@ -1657,6 +1659,14 @@ public boolean isPurified() { return acidity < 0.05F; } + public void setModified(boolean modified) { + this.modified = modified; + } + + public boolean isModified() { + return modified; + } + /** * @return A {@link Map} containing all the data necessary for use in {@link ConfigurationMessage}. */ diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java index 3265883b..f695aee6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java @@ -153,9 +153,7 @@ private void loadZone(File file) { } public void saveZones() { - for(Zone zone : getZones()) { - saveZone(zone); - } + zones.values().stream().filter(Zone::isModified).forEach(this::saveZone); } public void saveZone(Zone zone) { @@ -175,6 +173,7 @@ public void saveZone(Zone zone) { Files.write(new File(file, "metablocks.json").toPath(), metaBlocksBytes); Files.write(new File(file, "config.json").toPath(), configBytes); Files.write(new File(file, "zone.dat").toPath(), dataBytes); + zone.setModified(false); } catch(Exception e) { logger.error(SERVER_MARKER, "Zone save failure. id: {}", zone.getDocumentId(), e); } From 2c10388684fdf6b3ad3c06307630a7cfa0656514 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Mon, 21 Oct 2024 23:57:05 +0200 Subject: [PATCH 097/176] Increase autosave interval from 30 seconds to 5 minutes --- gameserver/src/main/java/brainwine/gameserver/GameServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/GameServer.java b/gameserver/src/main/java/brainwine/gameserver/GameServer.java index ebb8f253..e29fdf38 100644 --- a/gameserver/src/main/java/brainwine/gameserver/GameServer.java +++ b/gameserver/src/main/java/brainwine/gameserver/GameServer.java @@ -25,7 +25,7 @@ public class GameServer implements CommandExecutor { - public static final int GLOBAL_SAVE_INTERVAL = 30000; // 30 seconds + public static final int GLOBAL_SAVE_INTERVAL = 300000; // 5 minutes private static final Logger logger = LogManager.getLogger(); private static GameServer instance; private final Thread handlerThread; From 6713a6f130960f01b345d44ca214529e389b6855 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 27 Oct 2024 01:29:38 +0200 Subject: [PATCH 098/176] Fix potential NPC kill assist crash...probably --- .../java/brainwine/gameserver/entity/npc/Npc.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java index 10beac9e..4d5b0639 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java @@ -188,14 +188,13 @@ public void die(EntityAttack cause) { if(!isPlayerPlaced()) { // Track assists - for(EntityAttack recentAttack : recentAttacks) { - Entity attacker = recentAttack.getAttacker(); - - if(attacker != player && attacker.isPlayer()) { - ((Player)attacker).getStatistics().trackAssist(config); - } - } + recentAttacks.stream() + .filter(attack -> attack.getAttacker() != killer && attack.getAttacker() instanceof Player) + .map(attack -> (Player)attack.getAttacker()) + .distinct() // TODO might be expensive + .forEach(attacker -> attacker.getStatistics().trackAssist(config)); + // Track kill player.getStatistics().trackKill(config); } From 06867d927a8e44c533bedf4240f50ababa2dff40 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 27 Oct 2024 02:00:49 +0100 Subject: [PATCH 099/176] Reset prefab block mod if replaced item mod type is different --- gameserver/src/main/java/brainwine/gameserver/zone/Zone.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 5140dc88..6ca6ae7c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -714,8 +714,8 @@ public void placePrefab(Prefab prefab, int x, int y, Random random, long seed) { 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 backMod = backItem.getMod() == block.getBackItem().getMod() ? block.getBackMod() : 0; + int frontMod = frontItem.getMod() == block.getFrontItem().getMod() ? block.getFrontMod() : 0; int liquidMod = block.getLiquidMod(); // Update base item if it isn't empty From 46bad3eac2955b0ca788e9d46232cff0342d9cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Thu, 31 Oct 2024 01:26:27 +0100 Subject: [PATCH 100/176] Add min and max acidity to EntitySpawn (#64) --- .../gameserver/zone/EntityManager.java | 9 +++++---- .../brainwine/gameserver/zone/EntitySpawn.java | 15 +++++++++++++++ gameserver/src/main/resources/spawning.json | 18 +++++++++--------- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java index 51b53fca..0c2ff5cf 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java @@ -70,19 +70,20 @@ public static void loadEntitySpawns() { } } - private static List getEligibleEntitySpawns(Biome biome, String locale, double depth, Item baseItem) { + private static List getEligibleEntitySpawns(Biome biome, String locale, double depth, double acidity, Item baseItem) { return spawns.entrySet().stream() .filter(entry -> entry.getKey() == biome) .map(Entry::getValue) .flatMap(Collection::stream) .filter(spawn -> locale.equalsIgnoreCase(spawn.getLocale()) && depth >= spawn.getMinDepth() && depth <= spawn.getMaxDepth() + && acidity >= spawn.getMinAcidity() && acidity <= spawn.getMaxAcidity() && ((!baseItem.hasId("base/maw") && !baseItem.hasId("base/pipe")) || spawn.getOrifice() == baseItem)) .collect(Collectors.toList()); } - private static EntitySpawn getRandomEligibleEntitySpawn(Biome biome, String locale, double depth, Item baseItem) { - return new WeightedMap<>(getEligibleEntitySpawns(biome, locale, depth, baseItem), EntitySpawn::getFrequency).next(); + private static EntitySpawn getRandomEligibleEntitySpawn(Biome biome, String locale, double depth, double acidity, Item baseItem) { + return new WeightedMap<>(getEligibleEntitySpawns(biome, locale, depth, acidity, baseItem), EntitySpawn::getFrequency).next(); } public void tick(float deltaTime) { @@ -132,7 +133,7 @@ private void spawnRandomEntity() { Block block = chunk.getBlock(x, y); String locale = block.getBaseItem().isAir() ? "sky" : "cave"; EntitySpawn spawn = getRandomEligibleEntitySpawn( - zone.getBiome(), locale, y / (double)zone.getHeight(), block.getBaseItem()); + zone.getBiome(), locale, y / (double)zone.getHeight(), zone.getAcidity(), block.getBaseItem()); if(immediate) { if(tryBustOrifice(x, y, Layer.BACK) || tryBustOrifice(x, y, Layer.FRONT)) { diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/EntitySpawn.java b/gameserver/src/main/java/brainwine/gameserver/zone/EntitySpawn.java index 0b2fe792..8908ea81 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/EntitySpawn.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/EntitySpawn.java @@ -21,6 +21,12 @@ public class EntitySpawn { @JsonProperty("max_depth") private double maxDepth = 1; + + @JsonProperty("min_acidity") + private double minAcidity = 0.0; + + @JsonProperty("max_acidity") + private double maxAcidity = 1.0; @JsonProperty("orifice") private Item orifice; @@ -43,6 +49,14 @@ public double getMinDepth() { public double getMaxDepth() { return maxDepth; } + + public double getMinAcidity() { + return minAcidity; + } + + public double getMaxAcidity() { + return maxAcidity; + } public Item getOrifice() { return orifice; @@ -51,4 +65,5 @@ public Item getOrifice() { public double getFrequency() { return frequency; } + } diff --git a/gameserver/src/main/resources/spawning.json b/gameserver/src/main/resources/spawning.json index 32c72601..71c2e71f 100644 --- a/gameserver/src/main/resources/spawning.json +++ b/gameserver/src/main/resources/spawning.json @@ -29,55 +29,55 @@ { "entity": "creatures/bluejay", "locale": "sky", - "purification": 1.0, + "max_acidity": 0.05, "frequency": 3 }, { "entity": "creatures/cardinal", "locale": "sky", - "purification": 1.0, + "max_acidity": 0.05, "frequency": 1 }, { "entity": "creatures/seagull", "locale": "sky", - "purification": 1.0, + "max_acidity": 0.05, "frequency": 2 }, { "entity": "creatures/butterfly-monarch", "locale": "sky", - "purification": 1.0, + "max_acidity": 0.05, "frequency":23 }, { "entity": "creatures/papilio-ulysses", "locale": "sky", - "purification": 1.0, + "max_acidity": 0.05, "frequency": 0.5 }, { "entity": "creatures/butterfly-swallowtail", "locale": "sky", - "purification": 1.0, + "max_acidity": 0.05, "frequency": 1 }, { "entity": "creatures/butterfly-moth", "locale": "sky", - "purification": 1.0, + "max_acidity": 0.05, "frequency": 5 }, { "entity": "creatures/butterfly-owl", "locale": "sky", - "purification": 1.0, + "max_acidity": 0.05, "frequency": 0.1 }, { "entity": "creatures/butterfly-paper-kite", "locale": "sky", - "purification": 1.0, + "max_acidity": 0.05, "frequency": 0.01 }, { From 22ca9c5fdcdc1f4df7c3a135ab3b1968dbc60843 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:11:21 +0100 Subject: [PATCH 101/176] Force inventory update if workshop item is crafted --- .../brainwine/gameserver/server/requests/CraftRequest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/CraftRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/CraftRequest.java index df734c36..618f8da1 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/CraftRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/CraftRequest.java @@ -94,11 +94,11 @@ public void process(Player player) { } for(CraftingRequirement ingredient : ingredients) { - inventory.removeItem(ingredient.getItem(), ingredient.getQuantity() * quantity); + inventory.removeItem(ingredient.getItem(), ingredient.getQuantity() * quantity, item.requiresWorkshop()); } int totalQuantity = item.getCraftingQuantity() * quantity; - inventory.addItem(item, totalQuantity); + inventory.addItem(item, totalQuantity, item.requiresWorkshop()); player.getStatistics().trackItemCrafted(item, totalQuantity); } } From 3dadb525a2416afc2cfd56a1febe6ef7e02ec868 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 5 Nov 2024 00:58:06 +0100 Subject: [PATCH 102/176] Prepare for unforeseen consequences (#57) --- .../gameserver/item/interactions/ContainerInteraction.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java index abcd0887..5f63d400 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java @@ -37,8 +37,8 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i Player player = (Player)entity; String dungeonId = metaBlock.getStringProperty("@"); - // Check if container is protected by a dungeon - if(item.hasUse(ItemUseType.FIELDABLE) && dungeonId != null && zone.isDungeonIntact(dungeonId)) { + // Check if container is protected + if(item.hasUse(ItemUseType.FIELDABLE) && (zone.isBlockProtected(x, y, player) || (dungeonId != null && zone.isDungeonIntact(dungeonId)))) { player.notify("This container is secured by protectors in the area."); return; } From d332f294a637ffb25de1dcad261f25fd0bf84062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Thu, 28 Nov 2024 17:17:23 +0100 Subject: [PATCH 103/176] Landmark voting (#65) --- .../gameserver/achievement/Achievement.java | 4 +- .../achievement/ArchitectAchievement.java | 15 ++++ .../achievement/VotingAchievement.java | 17 ++++ .../gameserver/item/ItemUseType.java | 2 + .../interactions/LandmarkInteraction.java | 83 +++++++++++++++++++ .../brainwine/gameserver/player/Player.java | 11 ++- .../gameserver/player/PlayerStatistics.java | 22 +++++ .../server/requests/BlockUseRequest.java | 3 + .../java/brainwine/gameserver/zone/Zone.java | 2 +- 9 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/achievement/ArchitectAchievement.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/achievement/VotingAchievement.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/LandmarkInteraction.java diff --git a/gameserver/src/main/java/brainwine/gameserver/achievement/Achievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/Achievement.java index a643e901..c1c01548 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievement/Achievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/Achievement.java @@ -31,7 +31,9 @@ @Type(name = "UndertakerAchievement", value = UndertakerAchievement.class), @Type(name = "DeliveranceAchievement", value = DeliveranceAchievement.class), @Type(name = "TrappingAchievement", value = TrappingAchievement.class), - @Type(name = "Journeyman", value = JourneymanAchievement.class) + @Type(name = "Journeyman", value = JourneymanAchievement.class), + @Type(name = "ArchitectAchievement", value = ArchitectAchievement.class), + @Type(name = "VotingAchievement", value = VotingAchievement.class), }) @JsonSerialize(using = AchievementSerializer.class) @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/gameserver/src/main/java/brainwine/gameserver/achievement/ArchitectAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/ArchitectAchievement.java new file mode 100644 index 00000000..78c24be5 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/ArchitectAchievement.java @@ -0,0 +1,15 @@ +package brainwine.gameserver.achievement; + +import brainwine.gameserver.player.Player; +import com.fasterxml.jackson.annotation.JacksonInject; + +public class ArchitectAchievement extends Achievement { + public ArchitectAchievement(@JacksonInject("title") String title) { + super(title); + } + + @Override + public int getProgress(Player player) { + return player.getStatistics().getLandmarkVotesReceived(); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/achievement/VotingAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/VotingAchievement.java new file mode 100644 index 00000000..f2491d5a --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/VotingAchievement.java @@ -0,0 +1,17 @@ +package brainwine.gameserver.achievement; + +import brainwine.gameserver.player.Player; +import com.fasterxml.jackson.annotation.JacksonInject; + +public class VotingAchievement extends Achievement { + + public VotingAchievement(@JacksonInject("title") String title) { + super(title); + } + + @Override + public int getProgress(Player player) { + return player.getStatistics().getLandmarksUpvoted(); + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java index e05f7a9b..fb3ae371 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java @@ -11,6 +11,7 @@ import brainwine.gameserver.item.interactions.ExpiatorInteraction; import brainwine.gameserver.item.interactions.GeckInteraction; import brainwine.gameserver.item.interactions.ItemInteraction; +import brainwine.gameserver.item.interactions.LandmarkInteraction; import brainwine.gameserver.item.interactions.NoteInteraction; import brainwine.gameserver.item.interactions.RecyclerInteraction; import brainwine.gameserver.item.interactions.SpawnInteraction; @@ -38,6 +39,7 @@ public enum ItemUseType { CHANGE(new ChangeInteraction()), FIELDABLE, FLY, + LANDMARK(new LandmarkInteraction()), MOVE, MULTI, NOTE(new NoteInteraction()), diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/LandmarkInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/LandmarkInteraction.java new file mode 100644 index 00000000..9dd0419a --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/LandmarkInteraction.java @@ -0,0 +1,83 @@ +package brainwine.gameserver.item.interactions; + +import brainwine.gameserver.dialog.Dialog; +import brainwine.gameserver.dialog.DialogHelper; +import brainwine.gameserver.dialog.DialogSection; +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.Layer; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.util.MapHelper; +import brainwine.gameserver.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; + +import java.util.HashMap; +import java.util.Map; + +public class LandmarkInteraction implements ItemInteraction { + private static final int VOTING_INTERVAL = 1000; + @Override + public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, Object config, Object[] data) { + if(!entity.isPlayer()) return; + + // Do nothing if data is invalid + if(data != null) { + return; + } + + Player player = (Player)entity; + + if(player.getLevel() < 10) { + player.notify("Sorry, you must be level 10 or higher to vote on landmarks."); + return; + } + + long now = System.currentTimeMillis(); + if(player.getLastLandmarkVoteAt() + VOTING_INTERVAL > now) { + player.notify("You must wait a bit before voting again."); + return; + } + + if(metaBlock.getOwner() == player) { + player.notify("Sorry, you cannot upvote your own landmark."); + return; + } + + Map v = MapHelper.getMap(metaBlock.getMetadata(), "v"); + if(v != null && v.containsKey(player.getDocumentId())) { + player.notify("You have already upvoted this landmark."); + return; + } + + String name = metaBlock.getStringProperty("n"); + Dialog dialog = new Dialog() + .setTitle("Landmark Upvote") + .setActions("Cancel", "Yes") + .addSection(new DialogSection().setTitle("Upvote " + name + "?")); + + player.showDialog(dialog, ans -> { + if(ans.length == 0) return; + if("Yes".equals(ans[0])) { + int current = metaBlock.getIntProperty("vc"); // will return 0 if null + metaBlock.setProperty("vc", current + 1); + + Map currentVotes = MapHelper.getMap(metaBlock.getMetadata(), "v", new HashMap<>()); + currentVotes.put(player.getDocumentId(), now); + metaBlock.getMetadata().put("v", currentVotes); + zone.updateBlockMod(metaBlock.getX(), metaBlock.getY(), Layer.FRONT, 1); + zone.sendBlockMetaUpdate(metaBlock); + + player.setLastLandmarkVoteAt(now); + player.addExperience(10); + player.getStatistics().trackLandmarksUpvoted(); + + Player owner = metaBlock.getOwner(); + if(owner != null) { + owner.getStatistics().trackLandmarkVotesReceived(); + } + + player.showDialog(DialogHelper.messageDialog("Vote Received", "Thanks for your upvote!")); + } + }); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index bed73341..468a7a55 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -122,6 +122,7 @@ public class Player extends Entity implements CommandExecutor { private boolean customSpawn; private long lastHeartbeat; private long lastTrackedEntityUpdate; + private long lastLandmarkVoteAt; private Zone nextZone; private Connection connection; @@ -1321,7 +1322,15 @@ private void updateTrackedEntities() { trackedEntities.clear(); trackedEntities.addAll(entitiesInRange); } - + + public long getLastLandmarkVoteAt() { + return lastLandmarkVoteAt; + } + + public void setLastLandmarkVoteAt(long lastLandmarkVoteAt) { + this.lastLandmarkVoteAt = lastLandmarkVoteAt; + } + public boolean isTrackingEntity(Entity entity) { return trackedEntities.contains(entity); } diff --git a/gameserver/src/main/java/brainwine/gameserver/player/PlayerStatistics.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerStatistics.java index 3e548f64..a667c3c5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/PlayerStatistics.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerStatistics.java @@ -24,6 +24,8 @@ import brainwine.gameserver.achievement.SpawnerStoppageAchievement; import brainwine.gameserver.achievement.TrappingAchievement; import brainwine.gameserver.achievement.UndertakerAchievement; +import brainwine.gameserver.achievement.ArchitectAchievement; +import brainwine.gameserver.achievement.VotingAchievement; import brainwine.gameserver.entity.EntityConfig; import brainwine.gameserver.item.Item; @@ -47,6 +49,8 @@ public class PlayerStatistics { private int undertakings; private int deliverances; private int deaths; + private int landmarksUpvoted; + private int landmarkVotesReceived; @JsonIgnore private Player player; @@ -391,6 +395,24 @@ public void trackDeliverances(int amount) { player.addExperience(25 * amount); player.updateAchievementProgress(DeliveranceAchievement.class); } + + public void trackLandmarksUpvoted() { + landmarksUpvoted++; + player.updateAchievementProgress(VotingAchievement.class); + } + + public int getLandmarkVotesReceived() { + return landmarkVotesReceived; + } + + public void trackLandmarkVotesReceived() { + landmarkVotesReceived++; + player.updateAchievementProgress(ArchitectAchievement.class); + } + + public int getLandmarksUpvoted() { + return landmarksUpvoted; + } public void setDeliverances(int deliverances) { this.deliverances = deliverances; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockUseRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockUseRequest.java index 4cde5329..73318b40 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockUseRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockUseRequest.java @@ -65,6 +65,9 @@ public void process(Player player) { case "note": ItemUseType.NOTE.getInteraction().interact(zone, player, x, y, layer, item, mod, metaBlock, null, data); break; + case "landmark": + ItemUseType.LANDMARK.getInteraction().interact(zone, player, x, y, layer, item, mod, metaBlock, null, data); + break; default: break; } } else { diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 6ca6ae7c..9c3abedd 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -1036,7 +1036,7 @@ public void updateBlock(int x, int y, Layer layer, Item item, int mod, Player ow } // TODO better block update methods - protected void updateBlockMod(int x, int y, Layer layer, int mod) { + public void updateBlockMod(int x, int y, Layer layer, int mod) { if(!areCoordinatesInBounds(x, y)) { return; } From fbdaca2fe33143a990a4a8d669d6245a8d385e4a Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Fri, 29 Nov 2024 20:54:51 +0100 Subject: [PATCH 104/176] Change `zone` into path param in `/v1/map` API request --- api/src/main/java/brainwine/api/PortalService.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/brainwine/api/PortalService.java b/api/src/main/java/brainwine/api/PortalService.java index 42698294..60f2b07b 100644 --- a/api/src/main/java/brainwine/api/PortalService.java +++ b/api/src/main/java/brainwine/api/PortalService.java @@ -39,7 +39,7 @@ public PortalService(Api api, int port) { logger.info(SERVER_MARKER, "Starting PortalService @ port {} ...", port); portal = Javalin.create(config -> config.jsonMapper(new JavalinJackson(JsonHelper.MAPPER))) .exception(Exception.class, this::handleException) - .get("/v1/map", this::handleMapRequest) + .get("/v1/map/{zone}", this::handleMapRequest) .get("/v1/worlds", this::handleZoneSearch) .start(port); } @@ -63,15 +63,8 @@ private void handleMapRequest(Context ctx) throws IOException { error(ctx, "A valid api token is required for this request."); return; } - - String nameOrId = ctx.queryParam("zone"); - - if(nameOrId == null) { - error(ctx, "Zone not specified."); - return; - } - - ZoneInfo zone = dataFetcher.getZoneInfo(nameOrId); + + ZoneInfo zone = dataFetcher.getZoneInfo(ctx.pathParam("zone")); if(zone == null) { error(ctx, "Zone not found."); From 78cc7abc5e9a5ae580212dab78a29861fb31917f Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Fri, 29 Nov 2024 20:59:56 +0100 Subject: [PATCH 105/176] WIP string generator --- .../java/brainwine/gameserver/Naming.java | 1 + .../brainwine/gameserver/StringGenerator.java | 76 +++++++++++++++++++ .../gameserver/zone/gen/ZoneGenerator.java | 18 ++--- 3 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/StringGenerator.java diff --git a/gameserver/src/main/java/brainwine/gameserver/Naming.java b/gameserver/src/main/java/brainwine/gameserver/Naming.java index 48cc0a28..093eb64a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/Naming.java +++ b/gameserver/src/main/java/brainwine/gameserver/Naming.java @@ -5,6 +5,7 @@ * * Entity names are sourced from: https://github.com/bytebin/deepworld-gameserver/blob/master/config/fake.yml */ +@Deprecated public class Naming { private static final String[] ZONE_FIRST_NAMES = { diff --git a/gameserver/src/main/java/brainwine/gameserver/StringGenerator.java b/gameserver/src/main/java/brainwine/gameserver/StringGenerator.java new file mode 100644 index 00000000..d817a301 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/StringGenerator.java @@ -0,0 +1,76 @@ +package brainwine.gameserver; + +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Function; + +public class StringGenerator { + + private static final String[] ZONE_PREFIXES = { + "East", "Fort", "Lower", "Mount", "New", "North", "Old", "South", "St.", "Upper", "West" + }; + + public static final String[] ZONE_SUFFIXES = { + "Bluff", "Bridge", "Canyon", "Court", "Cove", "Crossing", "Dale", "Dell", "Falls", "Field", + "Glade", "Glen", "Gorge", "Grove", "Heights", "Hills", "Hollow", "Keep", "Mill", "Passage", + "Peak", "Place", "Point", "Ridge", "Shore", "Springs", "Vale", "Valley", "Village", "Way", + "Woods" + }; + + private static final String[] ZONE_NAMES = { + "Abroath", "Acton", "Ashington", "Awktill", "Ayrshire", "Bakewell", "Ballysud", "Bextrast", "Bifflesud", "Biggledrip", + "Bigsbyshire", "Bigsbythrow", "Birkenhead", "Bixnay", "Blackcastle", "Blantyre", "Brambleclum", "Bredlyclum", "Bredlystaple", "Bunnyworth", + "Burford", "Butterminster", "Butterward", "Buxenbridge", "Caithness", "Callington", "Canterhersh", "Canterswin", "Casterbib", "Chatterchurch", + "Chesterwint", "Chickerell", "Chippinggumby", "Chippingtad", "Chumshire", "Cindermead", "Coleford", "Combbridge", "Crackwardine", "Cratham", + "Crickmouster", "Crouchfold", "Croydon", "Crumbibby", "Cuddlekenne", "Cuddleton", "Dallyswaine", "Danderkale", "Dandylang", "Dantyfret", + "Dantyswade", "Darlington", "Desborough", "Didlythwaite", "Dingstrath", "Dodgefell", "Dorchester", "Dorfenhersh", "Dorfentory", "Drogheda", + "Dropsud", "Dundalk", "Dungarvan", "Edenbridge", "Fidgetbridge", "Flitwick", "Funderbridge", "Ginglingwent", "Gitbridge", "Godalming", + "Gravesend", "Hailsham", "Heathfield", "Heathshire", "Hertford", "Holmcombe", "Holmfit", "Holmtoft", "Hufflegander", "Hunlygin", + "Kilbigsby", "Killarney", "Knickermere", "Larkhall", "Larkshed", "Launceston", "Lechlade", "Leftkenne", "Littleblighter", "London", + "Martport", "Marvotippy", "Middleham", "Mildenhall", "Mosshersh", "Mumblecoddle", "Mumblehersh", "Mumslybost", "Natherthwaite", "Neekenne", + "Nerdlydin", "Newbury", "Nibcastle", "Orkney", "Padendigby", "Pegglekeld", "Petabinge", "Pettipane", "Pickering", "Pillway", + "Pillwint", "Potton", "Princeglum", "Puttermouth", "Putterwint", "Pyllchurch", "Richmond", "Rosesidle", "Rosewouth", "Rotherham", + "Russetcumby", "Saltbost", "Saltbourne", "Scrimpstring", "Scrimwan", "Scrumpbourne", "Sedbergh", "Shimshot", "Shingletip", "Skipton", + "Slughersh", "Slugwoddly", "Snortsdin", "Southam", "Southsea", "Southwick", "Southwold", "Specklewint", "Spilsby", "Stockport", + "Stockton", "Stompcoddle", "Stumpchurch", "Stumpclum", "Stumpswade", "Tanlyward", "Thumbsham", "Wadfret", "Wagfield", "Watton", + "Wetherby", "Willowtap", "Winchelsea", "Wittlewoddly", "Wixleybost", "Wixleywicket", "Wolsingham", "Woodbridge", "Xandercott", "Yarmouth", + "Yateley", "Yetterbury", "Yorkhang", "Yostercumby" + }; + + public static String getRandomZoneName() { + Random random = ThreadLocalRandom.current(); + int format = random.nextInt(3); + + switch(format) { + case 0: return ZONE_NAMES[random.nextInt(ZONE_NAMES.length)]; + case 1: return getRandomName(ZONE_PREFIXES, ZONE_NAMES); + case 2: return getRandomName(ZONE_NAMES, ZONE_SUFFIXES); + } + + return "Mystery Zone"; // Should not happen + } + + public static String getRandomZoneName(Function dupeCheck, int maxRetries) { + String name = getRandomZoneName(); + int retries = 0; + + if(dupeCheck == null) { + return name; + } + + while(dupeCheck.apply(name)) { + if(retries >= maxRetries) { + return null; + } + + name = getRandomZoneName(); + retries++; + } + + return name; + } + + private static String getRandomName(String[] first, String[] second) { + return String.format("%s %s", first[(int)(Math.random() * first.length)], second[(int)(Math.random() * second.length)]); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java index 17e55cb9..3f5d18f6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java @@ -13,6 +13,7 @@ import brainwine.gameserver.GameServer; import brainwine.gameserver.Naming; +import brainwine.gameserver.StringGenerator; import brainwine.gameserver.item.Layer; import brainwine.gameserver.resource.Resource; import brainwine.gameserver.resource.ResourceFinder; @@ -127,18 +128,11 @@ public Zone generateZone(Biome biome, int width, int height) { public Zone generateZone(Biome biome, int width, int height, int seed) { String id = generateDocumentId(seed); - String name = Naming.getRandomZoneName(); - int retryCount = 0; - - while(GameServer.getInstance().getZoneManager().getZoneByName(name) != null) { - if(retryCount >= 10) { - name = id; - logger.warn(SERVER_MARKER, "Could not generate a unique name for zone {}", id); - break; - } - - name = Naming.getRandomZoneName(); - retryCount++; + String name = StringGenerator.getRandomZoneName(x -> GameServer.getInstance().getZoneManager().getZoneByName(x) != null, 20); + + if(name == null) { + name = id; + logger.warn(SERVER_MARKER, "Could not generate a unique name for zone {}", id); } Zone zone = new Zone(id, name, biome, width, height); From f5b54dce09dd30ac095968520f40456dbea68f04 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 30 Nov 2024 04:39:58 +0100 Subject: [PATCH 106/176] Update submodule deepworld-config --- deepworld-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepworld-config b/deepworld-config index 6052c539..665b1187 160000 --- a/deepworld-config +++ b/deepworld-config @@ -1 +1 @@ -Subproject commit 6052c53944e3d4469819e725c59914ea136b2007 +Subproject commit 665b1187b5e645594ea80a986a7181904b938291 From e15e944741c7ff152051cbf0d62a4b71be74abde Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:33:58 +0100 Subject: [PATCH 107/176] Track structure generation --- .../java/brainwine/gameserver/zone/Zone.java | 5 +- .../gameserver/zone/gen/GeneratorContext.java | 35 +++++++++----- .../gameserver/zone/gen/models/Structure.java | 47 +++++++++++++++++++ .../gen/tasks/StructureGeneratorTask.java | 31 +++++++++--- 4 files changed, 98 insertions(+), 20 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/zone/gen/models/Structure.java diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 9c3abedd..a2c16ca6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -674,13 +674,16 @@ public void placePrefab(Prefab prefab, int x, int y, Random random) { } public void placePrefab(Prefab prefab, int x, int y, Random random, long seed) { + placePrefab(prefab, x, y, random, prefab.isMirrorable() && random.nextBoolean(), seed); + } + + public void placePrefab(Prefab prefab, int x, int y, Random random, boolean mirrored, long seed) { 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 replacedItems = new HashMap<>(); // Replacements diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/GeneratorContext.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/GeneratorContext.java index b6f219ad..4dd8cc35 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/GeneratorContext.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/GeneratorContext.java @@ -1,29 +1,27 @@ 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.util.SimplexNoise; -import brainwine.gameserver.util.Vector2i; import brainwine.gameserver.zone.Biome; import brainwine.gameserver.zone.Block; import brainwine.gameserver.zone.Chunk; import brainwine.gameserver.zone.Zone; import brainwine.gameserver.zone.gen.caves.Cave; +import brainwine.gameserver.zone.gen.models.Structure; import brainwine.gameserver.zone.gen.surface.SurfaceRegion; public class GeneratorContext { private final List surfaceRegions = new ArrayList<>(); private final List caves = new ArrayList<>(); - private final Map prefabRegions = new HashMap<>(); + private final List structures = new ArrayList<>(); private final Zone zone; private final int seed; private final Random random; @@ -61,8 +59,9 @@ public boolean placePrefab(Prefab prefab, int x, int y) { y = Math.max(1, Math.min(y, getHeight() - prefab.getHeight() - 3)); if(!willPrefabOverlap(prefab, x, y)) { - zone.placePrefab(prefab, x, y, random, seed); - prefabRegions.put(new Vector2i(x, y), new Vector2i(prefab.getWidth(), prefab.getHeight())); + boolean mirrored = random.nextBoolean(); + zone.placePrefab(prefab, x, y, random, mirrored, seed); + structures.add(new Structure(prefab, x, y, mirrored)); return true; } @@ -147,13 +146,8 @@ private void placeScaffolding(int x, int y, int width, boolean ruin) { } public boolean willPrefabOverlap(Prefab prefab, int x, int y) { - for(Entry entry : prefabRegions.entrySet()) { - Vector2i position = entry.getKey(); - Vector2i size = entry.getValue(); - int x2 = position.getX(); - int y2 = position.getY(); - - if(x + prefab.getWidth() >= x2 && x <= x2 + size.getX() && y + prefab.getHeight() >= y2 && y <= y2 + size.getY()) { + for(Structure structure : structures) { + if(x + prefab.getWidth() >= structure.getX() && x <= structure.getX() + structure.getWidth() && y + prefab.getHeight() >= structure.getY() && y <= structure.getY() + structure.getHeight()) { return true; } } @@ -161,6 +155,21 @@ public boolean willPrefabOverlap(Prefab prefab, int x, int y) { return false; } + public boolean isStructureMirrored(int x, int y) { + Structure structure = getStructure(x, y); + return structure != null && structure.isMirrored(); + } + + public Structure getStructure(int x, int y) { + for(Structure structure : structures) { + if(x >= structure.getX() && x <= structure.getX() + structure.getWidth() && y >= structure.getY() && y <= structure.getY() + structure.getHeight()) { + return structure; + } + } + + return null; + } + public void updateBlock(int x, int y, Layer layer, int item) { zone.updateBlock(x, y, layer, item); } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/models/Structure.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/models/Structure.java new file mode 100644 index 00000000..4156d364 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/models/Structure.java @@ -0,0 +1,47 @@ +package brainwine.gameserver.zone.gen.models; + +import brainwine.gameserver.prefab.Prefab; + +/** + * Instance of a placed structure in a zone for use in generator contexts. + * + * TODO we could store more information here in the future to allow for more specific structure edits after the fact. + */ +public class Structure { + + private final Prefab prefab; + private final int x; + private final int y; + private final boolean mirrored; + + public Structure(Prefab prefab, int x, int y, boolean mirrored) { + this.prefab = prefab; + this.x = x; + this.y = y; + this.mirrored = mirrored; + } + + public Prefab getPrefab() { + return prefab; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getWidth() { + return prefab.getWidth(); + } + + public int getHeight() { + return prefab.getHeight(); + } + + public boolean isMirrored() { + return mirrored; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java index 30a17b81..4c6a3d3c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java @@ -191,8 +191,10 @@ private void placeComponentChests(GeneratorContext ctx, List containe String dungeonId = container.getStringProperty("@"); int x = container.getX(); int y = container.getY(); - ctx.updateBlock(x, y, Layer.FRONT, "containers/chest-industrial", 1); - MetaBlock componentChest = ctx.getZone().getMetaBlock(x, y); + int offset = getContainerOffset(ctx, x, y); + ctx.updateBlock(x, y, Layer.FRONT, 0); + ctx.updateBlock(x + offset, y, Layer.FRONT, "containers/chest-industrial", 1); + MetaBlock componentChest = ctx.getZone().getMetaBlock(x + offset, y); componentChest.setProperty("@", dungeonId); componentChest.setProperty("$", iterator.next().getId()); iterator.remove(); @@ -213,8 +215,10 @@ private void placeBrokenTeleporters(GeneratorContext ctx, List contai MetaBlock container = containers.remove(0); int x = container.getX(); int y = container.getY(); - ctx.updateBlock(x, y, Layer.FRONT, "mechanical/teleporter"); - ctx.getZone().removeMetaBlock(x, y); // Broken teleporters should have no metadata + int offset = getContainerOffset(ctx, x, y); + ctx.updateBlock(x, y, Layer.FRONT, 0); + ctx.updateBlock(x + offset, y, Layer.FRONT, "mechanical/teleporter"); + ctx.getZone().removeMetaBlock(x + offset, y); // Broken teleporters should have no metadata } } @@ -226,17 +230,32 @@ private void placeInfernalProtectors(GeneratorContext ctx, List conta MetaBlock container = containers.remove(i); int x = container.getX(); int y = container.getY(); + int offset = getContainerOffset(ctx, x, y); // Replace with large chest if container is a small chest if(container.getItem().hasId("containers/chest")) { - ctx.updateBlock(x, y, Layer.FRONT, "containers/chest-large", 1, container.getMetadata()); + ctx.updateBlock(x, y, Layer.FRONT, 0); + ctx.updateBlock(x + offset, y, Layer.FRONT, "containers/chest-large", 1, container.getMetadata()); } // Place dish on top of the container - ctx.updateBlock(x, y - 1, Layer.FRONT, "hell/dish"); + ctx.updateBlock(x + offset, y - 1, Layer.FRONT, "hell/dish"); } } + /** + * Crappy fix for badly placed teleporters, component chests etc. in mirrored structures. + * + * @return X offset for large containers. + */ + private int getContainerOffset(GeneratorContext ctx, int x, int y) { + if(ctx.isOccupied(x - 1, y, Layer.FRONT) || ctx.getBlock(x, y).getFrontItem().getBlockWidth() > 1) { + return 0; // TODO will cause issues if original container is larger than 2 blocks + } + + return ctx.isStructureMirrored(x, y) ? -1 : 0; + } + private void placeRandomSpawnBuilding(GeneratorContext ctx, int x) { Prefab spawnBuilding = spawnBuildings.next(ctx.getRandom()); From 6853e9ff79f41477c86c5a6e44df419630f4565c Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Fri, 13 Dec 2024 11:15:41 +0100 Subject: [PATCH 108/176] Fix luck --- .../src/main/java/brainwine/gameserver/loot/LootManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/loot/LootManager.java b/gameserver/src/main/java/brainwine/gameserver/loot/LootManager.java index a55cac5c..e590efa9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/loot/LootManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/loot/LootManager.java @@ -55,7 +55,7 @@ public List getEligibleLoot(Player player, String... categories) { } public List getEligibleLoot(Player player, Collection categories) { - int luck = player.getSkillLevel(Skill.LUCK); + int luck = player.getTotalSkillLevel(Skill.LUCK); int minFrequency = luck > MIN_FREQUENCIES_BY_LUCK.length ? 1 : MIN_FREQUENCIES_BY_LUCK[luck - 1]; List eligibleLoot = lootTables.entrySet().stream() .filter(entry -> categories.contains(entry.getKey())) From b0450b777094f9341293135616a8cf4d421eafb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Sat, 14 Dec 2024 17:50:49 +0100 Subject: [PATCH 109/176] Player no longer sees a block being collected when using an hatchet (#67) --- .../brainwine/gameserver/server/requests/BlockMineRequest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index 6ae44a71..02934d1a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -101,6 +101,8 @@ public void process(Player player) { // Send inventory message for v3 players if(player.isV3()) { + player.sendDelayedMessage(new InventoryMessage(player.getInventory().getClientConfig(item))); + Item decayItem = item.getDecayInventoryItem(); if(!decayItem.isAir()) { From 8ceeafcb16186242a34b351931b7e00971561717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Sat, 14 Dec 2024 19:43:45 +0100 Subject: [PATCH 110/176] Add drowning (#66) --- .../brainwine/gameserver/item/DamageType.java | 1 + .../gameserver/item/ItemUseType.java | 1 + .../gameserver/player/Inventory.java | 12 +++-- .../brainwine/gameserver/player/Player.java | 45 ++++++++++++++++++- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/DamageType.java b/gameserver/src/main/java/brainwine/gameserver/item/DamageType.java index b4dee83a..1f3ab4d1 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/DamageType.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/DamageType.java @@ -13,6 +13,7 @@ public enum DamageType { FIRE, PIERCING, SLASHING, + SUFFOCATION, @JsonEnumDefaultValue NONE; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java index fb3ae371..66a0686a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java @@ -27,6 +27,7 @@ public enum ItemUseType { AFTERBURNER, + BREATH, BURST(new BurstInteraction()), COMPOSTER(new ComposterInteraction()), CONTAINER(new ContainerInteraction()), diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java index 3659e072..4a21b6c0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java @@ -149,17 +149,21 @@ public int getQuantity(Item item) { public boolean isEmpty() { return items.isEmpty(); } - - public Item findJetpack() { + + public Item findAccessoryWithUse(ItemUseType use) { for(Item item : accessories.getItems()) { - if(item.hasUse(ItemUseType.FLY)) { + if(item.hasUse(use)) { return item; } } - + return Item.AIR; } + public Item findJetpack() { + return findAccessoryWithUse(ItemUseType.FLY); + } + public ItemContainer getHotbar() { return hotbar; } diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index 468a7a55..db68543a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -14,6 +14,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; +import brainwine.gameserver.zone.Block; import com.fasterxml.jackson.annotation.JsonCreator; import brainwine.gameserver.GameConfiguration; @@ -31,6 +32,7 @@ import brainwine.gameserver.entity.EntityAttack; import brainwine.gameserver.entity.EntityStatus; import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.item.ItemUseType; @@ -113,6 +115,7 @@ public class Player extends Entity implements CommandExecutor { private TradeSession tradeSession; private Placement lastPlacement; private Item heldItem = Item.AIR; + private double breath = 1.0f; private int spawnX; private int spawnY; private int teleportX; @@ -120,6 +123,7 @@ public class Player extends Entity implements CommandExecutor { private boolean stealth; private boolean godMode; private boolean customSpawn; + private long lastBreathMessage; private long lastHeartbeat; private long lastTrackedEntityUpdate; private long lastLandmarkVoteAt; @@ -194,7 +198,11 @@ public void tick(float deltaTime) { if(!isDead() && now >= lastDamagedAt + REGEN_NO_DAMAGE_TIME) { heal(BASE_REGEN_AMOUNT * deltaTime); } - + + if(!isDead()) { + applyBreath(deltaTime); + } + // Try to timeout trade if(isTrading()) { tradeSession.timeout(); @@ -253,6 +261,41 @@ public void setHealth(float health) { super.setHealth(health); sendMessage(new HealthMessage(health)); } + + public double getBreathCapacity() { + return 15.0 + 1.25 * (getTotalSkillLevel(Skill.SURVIVAL) - 1); + } + + public boolean isSubmerged() { + Block headBlock = getZone().getBlock(getBlockX(), getBlockY() - 1); + + if(headBlock == null) return false; + + Item liquidItem = headBlock.getLiquidItem(); + + return !liquidItem.isAir() && headBlock.getLiquidMod() > 2; + } + + public void applyBreath(float deltaTime) { + Item breathItem = getInventory().findAccessoryWithUse(ItemUseType.BREATH); + if(!breathItem.isAir()) { + breath = 1.0; + } else { + if(isSubmerged()) { + breath -= deltaTime / getBreathCapacity(); + } else { + breath += deltaTime / 5.0; + } + breath = MathUtils.clamp(breath, 0.0, 1.0); + + long currentTime = System.currentTimeMillis(); + if(lastBreathMessage + 1000 < currentTime) { + sendMessage(new StatMessage("breath", breath)); + if(breath < 0.001) attack(null, null, 0.5f, DamageType.SUFFOCATION); + lastBreathMessage = currentTime; + } + } + } @Override public float getAttackMultiplier(EntityAttack attack) { From 0e8e16fb4a8b548add802bdf7e335ed251bc081e Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 15 Dec 2024 02:05:22 +0100 Subject: [PATCH 111/176] Add thirst --- .../brainwine/gameserver/player/Player.java | 51 ++++++++++++++++--- .../server/messages/StatMessage.java | 7 +-- .../gameserver/server/models/PlayerStat.java | 21 ++++++++ .../server/requests/TransactionRequest.java | 3 +- 4 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/server/models/PlayerStat.java diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index db68543a..f7e2a4b3 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -14,7 +14,6 @@ import java.util.function.Consumer; import java.util.stream.Collectors; -import brainwine.gameserver.zone.Block; import com.fasterxml.jackson.annotation.JsonCreator; import brainwine.gameserver.GameConfiguration; @@ -65,10 +64,13 @@ import brainwine.gameserver.server.messages.XpMessage; import brainwine.gameserver.server.messages.ZoneStatusMessage; import brainwine.gameserver.server.models.EntityStatusData; +import brainwine.gameserver.server.models.PlayerStat; import brainwine.gameserver.server.pipeline.Connection; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.MathUtils; import brainwine.gameserver.util.VersionUtils; +import brainwine.gameserver.zone.Biome; +import brainwine.gameserver.zone.Block; import brainwine.gameserver.zone.Chunk; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; @@ -115,7 +117,8 @@ public class Player extends Entity implements CommandExecutor { private TradeSession tradeSession; private Placement lastPlacement; private Item heldItem = Item.AIR; - private double breath = 1.0f; + private double breath = 1.0; + private double thirst; private int spawnX; private int spawnY; private int teleportX; @@ -124,6 +127,8 @@ public class Player extends Entity implements CommandExecutor { private boolean godMode; private boolean customSpawn; private long lastBreathMessage; + private long lastThirstMessage; + private long lastThirstDamageAt; private long lastHeartbeat; private long lastTrackedEntityUpdate; private long lastLandmarkVoteAt; @@ -201,6 +206,7 @@ public void tick(float deltaTime) { if(!isDead()) { applyBreath(deltaTime); + applyThirst(deltaTime); } // Try to timeout trade @@ -290,13 +296,44 @@ public void applyBreath(float deltaTime) { long currentTime = System.currentTimeMillis(); if(lastBreathMessage + 1000 < currentTime) { - sendMessage(new StatMessage("breath", breath)); + sendMessage(new StatMessage(PlayerStat.BREATH, breath)); if(breath < 0.001) attack(null, null, 0.5f, DamageType.SUFFOCATION); lastBreathMessage = currentTime; } } } + public void applyThirst(float deltaTime) { + long now = System.currentTimeMillis(); + double thirstPeriod = MathUtils.lerp(5.0, 10.0, (getTotalSkillLevel(Skill.SURVIVAL) - 1) / 6.0) * 60; + int direction = zone.getBiome() == Biome.DESERT && !zone.isPurified() ? 1 : -1; + thirst = MathUtils.clamp(thirst + (direction * deltaTime * (1.0 / thirstPeriod)), 0.0, 1.0); + + if(now > lastThirstMessage + 1000) { + sendMessage(new StatMessage(PlayerStat.THIRST, (float)thirst)); + lastThirstMessage = now; + } + + if(thirst >= 1.0) { + Item waterJar = ItemRegistry.getItem("containers/jar-water"); + + // Consume a jar of water if the player has any and reset thirst + if(inventory.hasItem(waterJar)) { + inventory.removeItem(waterJar, true); + inventory.addItem(ItemRegistry.getItem("containers/jar"), true); // Refund empty jar + notify(String.format("-1 %s", waterJar.getTitle())); + thirst = 0.0; + return; + } + + // Damage the player every 3 seconds instead if they have no water in their inventory + if(now > lastThirstDamageAt + 3000) { + attack(null, null, 0.25F, DamageType.FIRE, true); // Apply as true damage + lastThirstDamageAt = now; + } + } + } + @Override public float getAttackMultiplier(EntityAttack attack) { return isGodMode() ? 9999.0F : 1.0F; @@ -605,6 +642,8 @@ public void rubberband() { public void respawn() { if(isDead()) { setHealth(getMaxHealth()); + breath = 1.0; + thirst = 0.0; } sendMessage(new PlayerPositionMessage(spawnX, spawnY)); @@ -990,7 +1029,7 @@ public void setExperience(int experience, String message) { skillPoints += Math.max(0, newLevel - oldLevel); sendDelayedMessage(new LevelMessage(newLevel), 5000); sendDelayedMessage(new EffectMessage(0, 0, "levelup", 1), 5000); - sendDelayedMessage(new StatMessage("points", skillPoints), 5000); + sendDelayedMessage(new StatMessage(PlayerStat.POINTS, skillPoints), 5000); notifyPeers(String.format("%s leveled up to level %s!", name, newLevel), NotificationType.SYSTEM); } } @@ -1028,7 +1067,7 @@ public int getLevel() { public void setSkillPoints(int skillPoints) { this.skillPoints = skillPoints; - sendMessage(new StatMessage("points", skillPoints)); + sendMessage(new StatMessage(PlayerStat.POINTS, skillPoints)); } public int getSkillPoints() { @@ -1067,7 +1106,7 @@ public void removeCrowns(int crowns) { public void setCrowns(int crowns) { this.crowns = crowns; - sendMessage(new StatMessage("crowns", crowns)); + sendMessage(new StatMessage(PlayerStat.CROWNS, crowns)); } public int getCrowns() { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/StatMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/StatMessage.java index c2a27fc3..2e10eebf 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/StatMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/StatMessage.java @@ -2,15 +2,16 @@ import brainwine.gameserver.server.Message; import brainwine.gameserver.server.MessageInfo; +import brainwine.gameserver.server.models.PlayerStat; @MessageInfo(id = 44, collection = true) public class StatMessage extends Message { - public String key; + public PlayerStat stat; public Object value; - public StatMessage(String key, Object value) { - this.key = key; + public StatMessage(PlayerStat stat, Object value) { + this.stat = stat; this.value = value; } } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/models/PlayerStat.java b/gameserver/src/main/java/brainwine/gameserver/server/models/PlayerStat.java new file mode 100644 index 00000000..01be939a --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/server/models/PlayerStat.java @@ -0,0 +1,21 @@ +package brainwine.gameserver.server.models; + +import com.fasterxml.jackson.annotation.JsonValue; + +import brainwine.gameserver.server.messages.StatMessage; + +/** + * Easy enum for use with {@link StatMessage}. + */ +public enum PlayerStat { + + BREATH, + CROWNS, + POINTS, + THIRST; + + @JsonValue + public String getClientId() { + return toString().toLowerCase(); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/TransactionRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/TransactionRequest.java index 15cf08f6..81fbd2e0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/TransactionRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/TransactionRequest.java @@ -4,6 +4,7 @@ import brainwine.gameserver.server.PlayerRequest; import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.server.messages.StatMessage; +import brainwine.gameserver.server.models.PlayerStat; @RequestInfo(id = 41) public class TransactionRequest extends PlayerRequest { @@ -13,6 +14,6 @@ public class TransactionRequest extends PlayerRequest { @Override public void process(Player player) { player.notify("Sorry, the crown store has not been implemented yet."); - player.sendMessage(new StatMessage("crowns", player.getCrowns())); + player.sendMessage(new StatMessage(PlayerStat.CROWNS, player.getCrowns())); } } From 977e3fb300c5dc9fdd135309d97f96d646cf9f16 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 15 Dec 2024 03:17:49 +0100 Subject: [PATCH 112/176] Add freezing --- .../gameserver/item/ItemUseType.java | 2 ++ .../item/interactions/WarmthInteraction.java | 32 +++++++++++++++++++ .../brainwine/gameserver/player/Player.java | 28 +++++++++++++++- .../gameserver/server/models/PlayerStat.java | 1 + 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/WarmthInteraction.java diff --git a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java index 66a0686a..9f13ec28 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java @@ -20,6 +20,7 @@ import brainwine.gameserver.item.interactions.TargetTeleportInteraction; import brainwine.gameserver.item.interactions.TeleportInteraction; import brainwine.gameserver.item.interactions.TransmitInteraction; +import brainwine.gameserver.item.interactions.WarmthInteraction; /** * Much like with {@link Action}, block interactions depend on their use type. @@ -58,6 +59,7 @@ public enum ItemUseType { TRIGGER, TRANSMIT(new TransmitInteraction()), TRANSMITTED, + WARMTH(new WarmthInteraction()), ZONE_TELEPORT, @JsonEnumDefaultValue diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/WarmthInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/WarmthInteraction.java new file mode 100644 index 00000000..7ef4c495 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/WarmthInteraction.java @@ -0,0 +1,32 @@ +package brainwine.gameserver.item.interactions; + +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.Layer; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; + +/** + * Interaction handler for blocks that provide warmth + */ +public class WarmthInteraction implements ItemInteraction { + + @Override + public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, + Object config, Object[] data) { + // Do nothing if entity is not a player + if(!entity.isPlayer()) { + return; + } + + // Do nothing if object isn't lit or active + if(mod == 0) { + return; + } + + Player player = (Player)entity; + player.applyWarmth(); + player.notify("Ahh, toasty warmth."); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index f7e2a4b3..3dac4df9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -119,6 +119,7 @@ public class Player extends Entity implements CommandExecutor { private Item heldItem = Item.AIR; private double breath = 1.0; private double thirst; + private double cold; private int spawnX; private int spawnY; private int teleportX; @@ -129,6 +130,7 @@ public class Player extends Entity implements CommandExecutor { private long lastBreathMessage; private long lastThirstMessage; private long lastThirstDamageAt; + private long lastFreezeMessage; private long lastHeartbeat; private long lastTrackedEntityUpdate; private long lastLandmarkVoteAt; @@ -207,6 +209,7 @@ public void tick(float deltaTime) { if(!isDead()) { applyBreath(deltaTime); applyThirst(deltaTime); + applyFreeze(deltaTime); } // Try to timeout trade @@ -307,7 +310,7 @@ public void applyThirst(float deltaTime) { long now = System.currentTimeMillis(); double thirstPeriod = MathUtils.lerp(5.0, 10.0, (getTotalSkillLevel(Skill.SURVIVAL) - 1) / 6.0) * 60; int direction = zone.getBiome() == Biome.DESERT && !zone.isPurified() ? 1 : -1; - thirst = MathUtils.clamp(thirst + (direction * deltaTime * (1.0 / thirstPeriod)), 0.0, 1.0); + thirst = MathUtils.clamp(thirst + (direction * deltaTime / thirstPeriod), 0.0, 1.0); if(now > lastThirstMessage + 1000) { sendMessage(new StatMessage(PlayerStat.THIRST, (float)thirst)); @@ -334,6 +337,28 @@ public void applyThirst(float deltaTime) { } } + public void applyFreeze(float deltaTime) { + long now = System.currentTimeMillis(); + double freezePeriod = MathUtils.lerp(3.0, 10.0, (getTotalSkillLevel(Skill.SURVIVAL) - 1) / 6.0) * 60; + int direction = zone.getBiome() == Biome.ARCTIC ? 1 : -2; // Warm back up twice as fast + cold = MathUtils.clamp(cold + (direction * deltaTime / freezePeriod), 0.0, 1.0); + + // Send message & perform damage tick if it is time + if(now > lastFreezeMessage + 1000) { + if(cold >= 1.0) { + attack(null, null, 0.25F, DamageType.COLD, true); // Apply as true damage + } + + sendMessage(new StatMessage(PlayerStat.FREEZE, (float)cold)); + lastFreezeMessage = now; + } + } + + public void applyWarmth() { + cold = 0.0; + sendMessage(new StatMessage(PlayerStat.FREEZE, (float)cold)); + } + @Override public float getAttackMultiplier(EntityAttack attack) { return isGodMode() ? 9999.0F : 1.0F; @@ -644,6 +669,7 @@ public void respawn() { setHealth(getMaxHealth()); breath = 1.0; thirst = 0.0; + cold = 0.0; } sendMessage(new PlayerPositionMessage(spawnX, spawnY)); diff --git a/gameserver/src/main/java/brainwine/gameserver/server/models/PlayerStat.java b/gameserver/src/main/java/brainwine/gameserver/server/models/PlayerStat.java index 01be939a..6b2805f5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/models/PlayerStat.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/models/PlayerStat.java @@ -11,6 +11,7 @@ public enum PlayerStat { BREATH, CROWNS, + FREEZE, POINTS, THIRST; From ea91e6c31d80b4b547812fc21259da5d17b95e52 Mon Sep 17 00:00:00 2001 From: Kuroppoi Date: Tue, 17 Dec 2024 16:27:04 +0100 Subject: [PATCH 113/176] Fix potential crash --- .../gameserver/zone/gen/tasks/StructureGeneratorTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java index 4c6a3d3c..4384e25a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/tasks/StructureGeneratorTask.java @@ -227,7 +227,7 @@ private void placeInfernalProtectors(GeneratorContext ctx, List conta int amount = Math.min(containers.size(), Math.max(1, ctx.getWidth() * ctx.getHeight() / 100000)); for(int i = 0; i < amount; i++) { - MetaBlock container = containers.remove(i); + MetaBlock container = containers.remove(0); int x = container.getX(); int y = container.getY(); int offset = getContainerOffset(ctx, x, y); From e2b922c6f092aff85e79a758d158771a66df9cc2 Mon Sep 17 00:00:00 2001 From: Kuroppoi Date: Fri, 20 Dec 2024 17:14:15 +0100 Subject: [PATCH 114/176] Add crown store & zone ownership (#62) --- .../java/brainwine/api/PortalService.java | 24 +- .../java/brainwine/api/models/ZoneInfo.java | 35 +- .../gameserver/GameConfiguration.java | 6 + .../brainwine/gameserver/command/Command.java | 27 + .../command/world/WorldAddCommand.java | 47 ++ .../command/world/WorldCommand.java | 33 ++ .../command/world/WorldHelpCommand.java | 21 + .../command/world/WorldInfoCommand.java | 41 ++ .../command/world/WorldProtectedCommand.java | 59 +++ .../command/world/WorldPublicCommand.java | 47 ++ .../command/world/WorldPvpCommand.java | 47 ++ .../command/world/WorldRemoveCommand.java | 40 ++ .../command/world/WorldRenameCommand.java | 67 +++ .../java/brainwine/gameserver/item/Item.java | 7 + .../SpawnTeleportInteraction.java | 16 +- .../brainwine/gameserver/player/Player.java | 6 +- .../gameserver/player/PlayerManager.java | 11 +- .../server/messages/ZoneSearchMessage.java | 29 +- .../server/models/ZoneSearchData.java | 40 ++ .../server/requests/AuthenticateRequest.java | 9 +- .../server/requests/BlockMineRequest.java | 14 + .../server/requests/InventoryUseRequest.java | 2 +- .../server/requests/TransactionRequest.java | 4 +- .../server/requests/ZoneChangeRequest.java | 34 +- .../server/requests/ZoneSearchRequest.java | 113 +++-- .../gameserver/shop/ItemProduct.java | 58 +++ .../brainwine/gameserver/shop/Product.java | 50 ++ .../gameserver/shop/ProductImage.java | 59 +++ .../gameserver/shop/ShopManager.java | 126 +++++ .../gameserver/shop/ShopSection.java | 33 ++ .../gameserver/shop/ZoneProduct.java | 104 ++++ .../java/brainwine/gameserver/zone/Zone.java | 140 +++++- .../gameserver/zone/ZoneConfigFile.java | 66 ++- .../gameserver/zone/ZoneManager.java | 51 +- gameserver/src/main/resources/shop.json | 460 ++++++++++++++++++ .../java/brainwine/DirectDataFetcher.java | 9 +- 36 files changed, 1761 insertions(+), 174 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/command/world/WorldAddCommand.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/command/world/WorldCommand.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/command/world/WorldHelpCommand.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/command/world/WorldInfoCommand.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/command/world/WorldProtectedCommand.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/command/world/WorldPublicCommand.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/command/world/WorldPvpCommand.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/command/world/WorldRemoveCommand.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/command/world/WorldRenameCommand.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/server/models/ZoneSearchData.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/shop/ItemProduct.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/shop/Product.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/shop/ProductImage.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/shop/ShopManager.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/shop/ShopSection.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/shop/ZoneProduct.java create mode 100644 gameserver/src/main/resources/shop.json diff --git a/api/src/main/java/brainwine/api/PortalService.java b/api/src/main/java/brainwine/api/PortalService.java index 60f2b07b..68049d8a 100644 --- a/api/src/main/java/brainwine/api/PortalService.java +++ b/api/src/main/java/brainwine/api/PortalService.java @@ -107,7 +107,6 @@ private void handleMapRequest(Context ctx) throws IOException { * TODO could use some work. */ private void handleZoneSearch(Context ctx) { - final List zones = (List)dataFetcher.fetchZoneInfo(); String apiToken = ctx.queryParam("api_token"); if(apiToken == null || !dataFetcher.verifyApiToken(apiToken)) { @@ -115,6 +114,9 @@ private void handleZoneSearch(Context ctx) { return; } + final List zones = (List)dataFetcher.fetchZoneInfo(); // TODO this will probably be slow if there is a large number of zones + zones.removeIf(zone -> zone.isPrivate() && !apiToken.equals(zone.getOwner()) && !zone.getMembers().contains(apiToken)); + handleQueryParam(ctx, "name", String.class, name -> { zones.removeIf(zone -> !zone.getName().toLowerCase().contains(name.toLowerCase())); }); @@ -131,12 +133,22 @@ private void handleZoneSearch(Context ctx) { zones.removeIf(zone -> zone.isPvp() != pvp); }); - handleQueryParam(ctx, "protected", boolean.class, locked -> { - zones.removeIf(zone -> zone.isLocked() != locked); + handleQueryParam(ctx, "protected", boolean.class, value -> { + zones.removeIf(zone -> zone.isProtected() != value); }); handleQueryParam(ctx, "residency", String.class, residency -> { - zones.clear(); // not supported yet + switch(residency) { + case "owned": + zones.removeIf(zone -> !apiToken.equals(zone.getOwner())); + break; + case "member": + zones.removeIf(zone -> !zone.getMembers().contains(apiToken)); + break; + default: + zones.clear(); + break; + } }); handleQueryParam(ctx, "account", String.class, account -> { @@ -146,14 +158,12 @@ private void handleZoneSearch(Context ctx) { handleQueryParam(ctx, "sort", String.class, sort -> { switch(sort) { case "popularity": // Sort by most players first - zones.removeIf(zone -> zone.getPlayerCount() == 0); + //zones.removeIf(zone -> zone.getPlayerCount() == 0); zones.sort((a, b) -> Integer.compare(b.getPlayerCount(), a.getPlayerCount())); break; case "created": // Sort by newest first zones.sort((a, b) -> b.getCreationDate().compareTo(a.getCreationDate())); break; - case "development": // TODO - break; } }); diff --git a/api/src/main/java/brainwine/api/models/ZoneInfo.java b/api/src/main/java/brainwine/api/models/ZoneInfo.java index 5ee4daf0..41982c71 100644 --- a/api/src/main/java/brainwine/api/models/ZoneInfo.java +++ b/api/src/main/java/brainwine/api/models/ZoneInfo.java @@ -1,6 +1,8 @@ package brainwine.api.models; import java.time.OffsetDateTime; +import java.util.Collections; +import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; @@ -15,28 +17,34 @@ public class ZoneInfo { private final String activity; private final boolean pvp; private final boolean premium; - private final boolean locked; + private final boolean isPrivate; + private final boolean isProtected; private final int playerCount; private final int width; private final int height; private final int[] surface; private final double explorationProgress; private final OffsetDateTime creationDate; + private final String owner; + private final List members; - public ZoneInfo(String name, String biome, String activity, boolean pvp, boolean premium, boolean locked, - int playerCount, int width, int height, int[] surface, double explorationProgress, OffsetDateTime creationDate) { + public ZoneInfo(String name, String biome, String activity, boolean pvp, boolean premium, boolean isPrivate, boolean isProtected, + int playerCount, int width, int height, int[] surface, double explorationProgress, OffsetDateTime creationDate, String owner, List members) { this.name = name; this.biome = biome; this.activity = activity; this.pvp = pvp; this.premium = premium; - this.locked = locked; + this.isPrivate = isPrivate; + this.isProtected = isProtected; this.playerCount = playerCount; this.width = width; this.height = height; this.surface = surface; this.explorationProgress = explorationProgress; this.creationDate = creationDate; + this.owner = owner; + this.members = members; } public String getName() { @@ -59,9 +67,12 @@ public boolean isPremium() { return premium; } - @JsonProperty("protected") - public boolean isLocked() { - return locked; + public boolean isPrivate() { + return isPrivate; + } + + public boolean isProtected() { + return !isPrivate && isProtected; // Only display protection lock if world is public } @JsonProperty("players") @@ -93,4 +104,14 @@ public double getExplorationProgress() { public OffsetDateTime getCreationDate() { return creationDate; } + + @JsonIgnore + public String getOwner() { + return owner; + } + + @JsonIgnore + public List getMembers() { + return Collections.unmodifiableList(members); + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java b/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java index fe527439..2e33c8a0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java +++ b/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java @@ -27,6 +27,7 @@ import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.player.Player; import brainwine.gameserver.player.Skill; +import brainwine.gameserver.shop.ShopManager; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.VersionUtils; import brainwine.shared.JsonHelper; @@ -53,6 +54,7 @@ public static void init() { loadConfigOverrides(); logger.info(SERVER_MARKER, "Configuring ..."); configure(); + ShopManager.loadShopData(); logger.info(SERVER_MARKER, "Caching versioned configurations ..."); cacheVersionedConfigs(); logger.info(SERVER_MARKER, "Load complete! Took {} milliseconds", System.currentTimeMillis() - startTime); @@ -77,6 +79,10 @@ private static void configure() { MapHelper.put(baseConfig, "shop.currency", new HashMap<>()); Map items = MapHelper.getMap(baseConfig, "items"); + // Clear shop data + MapHelper.put(baseConfig, "shop.sections", new ArrayList<>()); + MapHelper.put(baseConfig, "shop.items", new ArrayList<>()); + // Add custom commands to the client config CommandManager.getCommandNames().forEach(command -> { MapHelper.put(baseConfig, String.format("commands.%s", command), true); diff --git a/gameserver/src/main/java/brainwine/gameserver/command/Command.java b/gameserver/src/main/java/brainwine/gameserver/command/Command.java index ddf30223..1e1b1661 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/Command.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/Command.java @@ -1,5 +1,7 @@ package brainwine.gameserver.command; +import static brainwine.gameserver.player.NotificationType.SYSTEM; + public abstract class Command { public abstract void execute(CommandExecutor executor, String[] args); @@ -8,4 +10,29 @@ public abstract class Command { public boolean canExecute(CommandExecutor executor) { return true; } + + protected final boolean checkArgumentCount(CommandExecutor executor, String[] args, int... counts) { + int highestCount = 0; + + for(int count : counts) { + if(count > highestCount) { + highestCount = count; + } + + if(args.length == count) { + return true; + } + } + + if(args.length > highestCount) { + return true; + } + + sendUsageMessage(executor); + return false; + } + + protected final void sendUsageMessage(CommandExecutor executor) { + executor.notify(String.format("Usage: %s", getUsage(executor)), SYSTEM); + } } diff --git a/gameserver/src/main/java/brainwine/gameserver/command/world/WorldAddCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldAddCommand.java new file mode 100644 index 00000000..d3860a8d --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldAddCommand.java @@ -0,0 +1,47 @@ +package brainwine.gameserver.command.world; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.Zone; + +@CommandInfo(name = "wadd", description = "Add a member to your private world.") +public class WorldAddCommand extends WorldCommand { + + @Override + public void execute(Zone zone, Player player, String[] args) { + if(!checkArgumentCount(player, args, 1)) { + return; + } + + Player target = GameServer.getInstance().getPlayerManager().getPlayer(args[0]); + + // Check if target exists + if(target == null) { + player.notify(String.format("Player '%s' not found.", args[0])); + return; + } + + // World owners cannot add themselves as members + if(zone.isOwner(target)) { + player.notify("You own this world and cannot add yourself as a member."); + return; + } + + // Check if target is already a member + if(zone.isMember(target)) { + player.notify(String.format("%s is already a member of this world.", target.getName())); + return; + } + + // TODO send feedback to target + zone.addMember(target); + player.notify(String.format("%s has been added.", target.getName())); + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/wadd "; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/command/world/WorldCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldCommand.java new file mode 100644 index 00000000..7c46e0f6 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldCommand.java @@ -0,0 +1,33 @@ +package brainwine.gameserver.command.world; + +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.Zone; + +/** + * Base class for commands regarding private world management. + */ +public abstract class WorldCommand extends Command { + + public abstract void execute(Zone zone, Player player, String[] args); + + @Override + public void execute(CommandExecutor executor, String[] args) { + Player player = (Player)executor; + Zone zone = player.getZone(); + + // Check if player owns world + if(!player.isGodMode() && !zone.isOwner(player)) { + player.notify("Sorry, you do not own this world."); + return; + } + + execute(zone, player, args); + } + + @Override + public boolean canExecute(CommandExecutor executor) { + return executor instanceof Player; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/command/world/WorldHelpCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldHelpCommand.java new file mode 100644 index 00000000..7a50173d --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldHelpCommand.java @@ -0,0 +1,21 @@ +package brainwine.gameserver.command.world; + +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.dialog.DialogHelper; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.Zone; + +@CommandInfo(name = "whelp", description = "Displays the world help menu.") +public class WorldHelpCommand extends WorldCommand { + + @Override + public void execute(Zone zone, Player player, String[] args) { + player.showDialog(DialogHelper.getDialog("world_help")); + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/whelp"; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/command/world/WorldInfoCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldInfoCommand.java new file mode 100644 index 00000000..58ba7001 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldInfoCommand.java @@ -0,0 +1,41 @@ +package brainwine.gameserver.command.world; + +import java.util.List; +import java.util.stream.Collectors; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.dialog.Dialog; +import brainwine.gameserver.dialog.DialogSection; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.Zone; + +@CommandInfo(name = "winfo", description = "Displays private world information.") +public class WorldInfoCommand extends WorldCommand { + + @Override + public void execute(Zone zone, Player player, String[] args) { + // Fetch member names + List memberNames = zone.getMembers().stream() + .map(x -> GameServer.getInstance().getPlayerManager().getPlayerById(x).getName()) + .collect(Collectors.toList()); + + // Create & show world info dialog + // TODO there's gotta be a cleaner way to do this... + Dialog dialog = new Dialog() + .addSection(new DialogSection().setTitle("World Info")) + .addSection(new DialogSection().setText(" ")) + .addSection(new DialogSection().setText(player.isV3() ? "Entry Code" : "Entry Code").setTextColor("4d5b82")) + .addSection(new DialogSection().setText("Entry codes are currently unavailable.")) + .addSection(new DialogSection().setText(" ")) + .addSection(new DialogSection().setText(player.isV3() ? "Members" : "Members").setTextColor("4d5b82")) + .addSection(new DialogSection().setText(memberNames.isEmpty() ? "None :(" : memberNames.toString().replaceAll("[\\[+\\]]", ""))); + player.showDialog(dialog); + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/winfo"; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/command/world/WorldProtectedCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldProtectedCommand.java new file mode 100644 index 00000000..4772aedb --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldProtectedCommand.java @@ -0,0 +1,59 @@ +package brainwine.gameserver.command.world; + +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.dialog.DialogHelper; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.Zone; + +@CommandInfo(name = "wprotected", description = "Turn off the protected status of a private world.") +public class WorldProtectedCommand extends WorldCommand { + + @Override + public void execute(Zone zone, Player player, String[] args) { + if(!checkArgumentCount(player, args, 1)) { + return; + } + + if(!args[0].equalsIgnoreCase("on") && !args[0].equalsIgnoreCase("off")) { + sendUsageMessage(player); + return; + } + + boolean value = args[0].equalsIgnoreCase("on"); + + if(value == zone.isProtected()) { + player.notify(String.format("Your world is already %s.", value ? "protected" : "unprotected")); + return; + } + + if(!value) { + // Show confirmation dialog + player.showDialog(DialogHelper.messageDialog("Are you sure?", + "WARNING: You cannot revert back to protected status once your world is unprotected. Are you sure you want to make it unprotected?") + .setActions("yesno"), input -> { + // Check cancellation + if(input.length == 1 && "cancel".equals(input[0])) { + return; + } + + // Disable world protection + zone.setProtected(false); + }); + } else { + // Deny request if player is not in god mode + if(!player.isGodMode()) { + player.notify("Sorry, you cannot revert your world back to protected status."); + return; + } + + // Enable world protection + zone.setProtected(true); + } + } + + @Override + public String getUsage(CommandExecutor executor) { + return executor.isAdmin() ? "/wprotected " : "/wprotected off"; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/command/world/WorldPublicCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldPublicCommand.java new file mode 100644 index 00000000..6a415375 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldPublicCommand.java @@ -0,0 +1,47 @@ +package brainwine.gameserver.command.world; + +import java.time.temporal.ChronoUnit; + +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.Zone; + +@CommandInfo(name = "wpublic", description = "Toggle world accessibility.") +public class WorldPublicCommand extends WorldCommand { + + public static final String ACTION_ID = "wpublic"; + + @Override + public void execute(Zone zone, Player player, String[] args) { + if(!checkArgumentCount(player, args, 1)) { + return; + } + + // Check if command is on cooldown + if(!player.isGodMode() && zone.isActionOnCooldown(ACTION_ID, 1, ChronoUnit.HOURS)) { + player.notify("Sorry, you can toggle accessibility only once an hour."); + return; + } + + if(!args[0].equalsIgnoreCase("on") && !args[0].equalsIgnoreCase("off")) { + sendUsageMessage(player); + return; + } + + boolean value = args[0].equalsIgnoreCase("on"); + + if(value == zone.isPublic()) { + player.notify(String.format("Your world is already %s.", value ? "public" : "private")); + return; + } + + zone.setPrivate(!value); + zone.recordActionTime(ACTION_ID); + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/wpublic "; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/command/world/WorldPvpCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldPvpCommand.java new file mode 100644 index 00000000..bca69332 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldPvpCommand.java @@ -0,0 +1,47 @@ +package brainwine.gameserver.command.world; + +import java.time.temporal.ChronoUnit; + +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.Zone; + +@CommandInfo(name = "wpvp", description = "Turn PvP on or off in a private world.") +public class WorldPvpCommand extends WorldCommand { + + public static final String ACTION_ID = "wpvp"; + + @Override + public void execute(Zone zone, Player player, String[] args) { + if(!checkArgumentCount(player, args, 1)) { + return; + } + + // Check if command is on cooldown + if(!player.isGodMode() && zone.isActionOnCooldown(ACTION_ID, 1, ChronoUnit.HOURS)) { + player.notify("Sorry, you can toggle PvP only once an hour."); + return; + } + + if(!args[0].equalsIgnoreCase("on") && !args[0].equalsIgnoreCase("off")) { + sendUsageMessage(player); + return; + } + + boolean value = args[0].equalsIgnoreCase("on"); + + if(value == zone.isPvp()) { + player.notify(String.format("PvP is already %s.", value ? "enabled" : "disabled")); + return; + } + + zone.setPvp(value); + zone.recordActionTime(ACTION_ID); + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/wpvp "; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/command/world/WorldRemoveCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldRemoveCommand.java new file mode 100644 index 00000000..92b2eacb --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldRemoveCommand.java @@ -0,0 +1,40 @@ +package brainwine.gameserver.command.world; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.Zone; + +@CommandInfo(name = "wremove", description = "Remove a member from your private world.") +public class WorldRemoveCommand extends WorldCommand { + + @Override + public void execute(Zone zone, Player player, String[] args) { + if(!checkArgumentCount(player, args, 1)) { + return; + } + + Player target = GameServer.getInstance().getPlayerManager().getPlayer(args[0]); + + // Check if target exists + if(target == null) { + player.notify(String.format("Player '%s' not found.", args[0])); + return; + } + + // Check if target is not a member + if(!zone.isMember(target)) { + player.notify(String.format("%s is not a member of this world.", target.getName())); + return; + } + + zone.removeMember(target); + player.notify(String.format("%s has been removed.", target.getName())); + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/wremove "; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/command/world/WorldRenameCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldRenameCommand.java new file mode 100644 index 00000000..4bd23488 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldRenameCommand.java @@ -0,0 +1,67 @@ +package brainwine.gameserver.command.world; + +import static brainwine.gameserver.player.NotificationType.SYSTEM; + +import java.time.temporal.ChronoUnit; +import java.util.regex.Pattern; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.Zone; +import brainwine.gameserver.zone.ZoneManager; + +@CommandInfo(name = "wrename", description = "Rename your private world.") +public class WorldRenameCommand extends WorldCommand { + + public static final String ACTION_ID = "wrename"; + private static final Pattern NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9 ]{5,20}$"); + + @Override + public void execute(Zone zone, Player player, String[] args) { + if(!checkArgumentCount(player, args, 1)) { + return; + } + + // Check if command is on cooldown + if(!player.isGodMode() && zone.isActionOnCooldown(ACTION_ID, 1, ChronoUnit.DAYS)) { + player.notify("Sorry, you can rename your world only once a day."); + return; + } + + ZoneManager zoneManager = GameServer.getInstance().getZoneManager(); + String name = String.join(" ", args).trim().replaceAll(" +", " "); + + // Verify name length + if(name.length() < 5 || name.length() > 20) { + player.notify("World name must be between 5 and 20 characters."); + return; + } + + // Verify name pattern + if(!NAME_PATTERN.matcher(name).matches()) { + player.notify("World name can only contain letters and numbers."); + return; + } + + // Check if name already exists + if(zoneManager.doesZoneExist(name)) { + player.notify(String.format("World name '%s' is already taken.", name)); + return; + } + + // Try rename zone (shouldn't fail) + if(!zoneManager.renameZone(zone, name)) { + player.notify("An unexpected problem occured while renaming your world.", SYSTEM); + return; + } + + zone.recordActionTime(ACTION_ID); + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/wrename "; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/item/Item.java b/gameserver/src/main/java/brainwine/gameserver/item/Item.java index 268bdb1d..24aadeb9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/Item.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/Item.java @@ -99,6 +99,9 @@ public class Item { @JsonProperty("placeover") private boolean placeover; + @JsonProperty("custom_mine") + private boolean customMine; + @JsonProperty("custom_place") private boolean customPlace; @@ -383,6 +386,10 @@ public boolean canPlaceOver() { return placeover; } + public boolean hasCustomMine() { + return customMine; + } + public boolean hasCustomPlace() { return customPlace; } diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnTeleportInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnTeleportInteraction.java index 3b07081e..c4f6fb80 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnTeleportInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SpawnTeleportInteraction.java @@ -1,11 +1,9 @@ package brainwine.gameserver.item.interactions; -import brainwine.gameserver.GameServer; import brainwine.gameserver.entity.Entity; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; import brainwine.gameserver.player.Player; -import brainwine.gameserver.zone.Biome; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; @@ -22,18 +20,8 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i return; } + // Send the player to a suitable beginner zone Player player = (Player)entity; - - // Find a random suitable zone - Zone targetZone = GameServer.getInstance().getZoneManager().getRandomZone(z -> z.getBiome() == Biome.PLAIN); - - // Notify the player if no zone could be found - if(targetZone == null) { - player.notify("Couldn't find a suitable zone to teleport to. Try again later."); - return; - } - - // Teleport the player to the target zone - player.changeZone(targetZone); + player.changeZone(null); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index 3dac4df9..430f0464 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -127,6 +127,7 @@ public class Player extends Entity implements CommandExecutor { private boolean stealth; private boolean godMode; private boolean customSpawn; + private boolean changingZones; private long lastBreathMessage; private long lastThirstMessage; private long lastThirstDamageAt; @@ -503,9 +504,10 @@ public void onDisconnect() { } // Are we switching zones? Then set the new zone. - if(nextZone != null) { + if(changingZones) { zone = nextZone; nextZone = null; + changingZones = false; } // Cancel existing trade session @@ -572,6 +574,7 @@ public void changeZone(Zone zone) { } public void changeZone(Zone zone, int x, int y) { + changingZones = true; nextZone = zone; spawnX = x; spawnY = y; @@ -1491,6 +1494,7 @@ public Map getClientConfig() { config.put("deaths", statistics.getDeaths()); config.put("appearance", appearance); config.put("settings", settings); + config.put("api_token", documentId); // Use document ID for now return config; } } diff --git a/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java index 1e928efb..163b89ba 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java @@ -16,7 +16,6 @@ import org.apache.logging.log4j.Logger; import org.mindrot.jbcrypt.BCrypt; -import brainwine.gameserver.GameServer; import brainwine.gameserver.server.pipeline.Connection; import brainwine.shared.JsonHelper; @@ -53,11 +52,6 @@ private void loadPlayer(File file) { try { PlayerConfigFile configFile = JsonHelper.readValue(file, PlayerConfigFile.class); Player player = new Player(id, configFile); - - if(player.getZone() == null) { - player.setZone(GameServer.getInstance().getZoneManager().getRandomZone()); - } - String name = player.getName(); if(playersByName.containsKey(name)) { @@ -94,7 +88,7 @@ public String register(String name) { } String id = UUID.randomUUID().toString(); - Player player = new Player(id, name, GameServer.getInstance().getZoneManager().getRandomZone()); // TODO tutorial zone + Player player = new Player(id, name, null); // TODO tutorial zone playersById.put(id, player); playersByName.put(name.toLowerCase(), player); String authToken = UUID.randomUUID().toString(); @@ -151,9 +145,8 @@ public void changePlayerName(Player player, String name) { } public void onPlayerConnect(Player player) { - Connection connection = player.getConnection(); onlinePlayers.add(player); - logger.info(SERVER_MARKER, "{} logged into zone {} from {}", player.getName(), player.getZone().getName(), connection.getAddress()); + logger.info(SERVER_MARKER, "{} logged into zone {}", player.getName(), player.getZone().getName()); } public void onPlayerDisconnect(Player player) { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneSearchMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneSearchMessage.java index 22c0f0d3..fe926287 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneSearchMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/ZoneSearchMessage.java @@ -1,13 +1,10 @@ package brainwine.gameserver.server.messages; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.List; import brainwine.gameserver.server.Message; import brainwine.gameserver.server.MessageInfo; -import brainwine.gameserver.zone.Zone; +import brainwine.gameserver.server.models.ZoneSearchData; @MessageInfo(id = 23) public class ZoneSearchMessage extends Message { @@ -15,30 +12,12 @@ public class ZoneSearchMessage extends Message { public String type; public int typePosition = 0; public int totalTypes = 1; - public List> zones; + public Collection zones; public int followeesActive; - public ZoneSearchMessage(String type, Collection zones, int followeesActive) { + public ZoneSearchMessage(String type, Collection zones, int followeesActive) { this.type = type; - this.zones = new ArrayList<>(); - - for(Zone zone : zones) { - List info = new ArrayList<>(); - info.add(zone.getName()); // Should actually be the document ID, but we change zones based on name. - info.add(zone.getName()); - info.add(zone.getPlayers().size()); - info.add(0); // followees count - info.add(Collections.EMPTY_LIST); // followees - info.add(0); // active duration - info.add((int)(zone.getExplorationProgress() * 100)); - info.add(zone.getBiome()); - info.add("purified"); - info.add("a"); // accessibility - info.add(0); // protection level - info.add(null); // scenario - this.zones.add(info); - } - + this.zones = zones; this.followeesActive = followeesActive; } } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/models/ZoneSearchData.java b/gameserver/src/main/java/brainwine/gameserver/server/models/ZoneSearchData.java new file mode 100644 index 00000000..e5435141 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/server/models/ZoneSearchData.java @@ -0,0 +1,40 @@ +package brainwine.gameserver.server.models; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; + +import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.Biome; +import brainwine.gameserver.zone.Zone; + +@JsonFormat(shape = Shape.ARRAY) +public class ZoneSearchData { + + public final String id; + public final String name; + public final int playerCount; + public final int followeeCount; + public final String[] followees; + public final int activeDuration; + public final int explorationProgress; + public final Biome biome; + public final String status; + public final String accessibility; // 'a' = all, 'p' = premium + public final int protectionLevel; + public final String scenario; // Market, PvP, etc. + + public ZoneSearchData(Zone zone, Player player) { + this.id = zone.getDocumentId(); + this.name = zone.getName(); + this.playerCount = zone.getPlayerCount(); + this.followeeCount = 0; // TODO + this.followees = new String[0]; // TODO + this.activeDuration = 0; // TODO + this.explorationProgress = (int)(zone.getExplorationProgress() * 100); + this.biome = zone.getBiome(); + this.status = zone.getBiome() == Biome.PLAIN ? (zone.isPurified() ? "purified" : "toxic") : null; + this.accessibility = "a"; + this.protectionLevel = zone.isProtected(player) ? 10 : 0; + this.scenario = zone.isPvp() ? "PvP" : null; // TODO market scenario + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/AuthenticateRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/AuthenticateRequest.java index 9d6bb837..d1db5bb2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/AuthenticateRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/AuthenticateRequest.java @@ -51,10 +51,9 @@ public void process(Connection connection) { return; } - // Try to put player in a random zone if current zone is null - // TODO default zone - if(zone == null) { - zone = server.getZoneManager().getRandomZone(); + // Try to put player in a random zone if current zone is null or cannot be joined + if(zone == null || !zone.canJoin(player)) { + zone = server.getZoneManager().findBeginnerZone(); } // Kick player if zone is still null (aka it failed to find a suitable random zone) @@ -65,8 +64,8 @@ public void process(Connection connection) { player.setConnection(connection); player.setClientVersion(version); - playerManager.onPlayerConnect(player); zone.addEntity(player); + playerManager.onPlayerConnect(player); }); }); } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index 02934d1a..c9bf8c6d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -89,6 +89,20 @@ public void process(Player player) { } } + // Check custom mine + if(item.hasCustomMine()) { + switch(item.getId()) { + case "mechanical/zone-teleporter": + if(!player.isGodMode() && zone.getMetaBlocksWithItem("mechanical/zone-teleporter").size() < 2) { + fail(player, "You must keep at least one world teleporter active."); + return; + } + + break; + default: break; + } + } + if(digging) { zone.digBlock(x, y); return; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java index db7b1486..ee5b3582 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java @@ -30,7 +30,7 @@ public class InventoryUseRequest extends PlayerRequest { @Override public void process(Player player) { // Don't do anything if the player is dead or doesn't own this item - if(player.isDead() || (!item.isAir() && !player.getInventory().hasItem(item))) { + if((player.isDead() && status == 1) || (!item.isAir() && !player.getInventory().hasItem(item))) { return; } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/TransactionRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/TransactionRequest.java index 81fbd2e0..dc1e1507 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/TransactionRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/TransactionRequest.java @@ -3,6 +3,7 @@ import brainwine.gameserver.player.Player; import brainwine.gameserver.server.PlayerRequest; import brainwine.gameserver.server.RequestInfo; +import brainwine.gameserver.shop.ShopManager; import brainwine.gameserver.server.messages.StatMessage; import brainwine.gameserver.server.models.PlayerStat; @@ -13,7 +14,6 @@ public class TransactionRequest extends PlayerRequest { @Override public void process(Player player) { - player.notify("Sorry, the crown store has not been implemented yet."); - player.sendMessage(new StatMessage(PlayerStat.CROWNS, player.getCrowns())); + ShopManager.purchaseProduct(player, key); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneChangeRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneChangeRequest.java index 02e3af7b..efa7bd4e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneChangeRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneChangeRequest.java @@ -5,35 +5,37 @@ import brainwine.gameserver.server.PlayerRequest; import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.zone.Zone; +import brainwine.gameserver.zone.ZoneManager; @RequestInfo(id = 24) public class ZoneChangeRequest extends PlayerRequest { - public String zoneName; + public String zoneId; @Override public void process(Player player) { - Zone zone = GameServer.getInstance().getZoneManager().getZoneByName(zoneName); + ZoneManager manager = GameServer.getInstance().getZoneManager(); + Zone zone = manager.getZone(zoneId); + // Get zone by name if ID search yielded no result if(zone == null) { - player.notify("Sorry, could not find a zone with name " + zoneName); + zone = manager.getZoneByName(zoneId); + } + + if(zone == null) { + player.notify("Couldn't locate world."); return; - } else if(zone == player.getZone()) { - player.notify("You're already in " + zoneName); + } + + if(zone == player.getZone()) { + player.notify("You're already in " + zone.getName()); return; } - // Check survival requirement unless player has god mode enabled - /* - if(!player.isGodMode()) { - Biome biome = zone.getBiome(); - int survival = MapHelper.getInt(GameConfiguration.getBaseConfig(), String.format("biomes.%s.survival_requirement", biome.getId())); - - if(player.getTotalSkillLevel(Skill.SURVIVAL) < survival) { - player.notify(String.format("Your survival skill needs to be at least level %s to enter %s worlds.", survival, biome.getId())); - return; - } - }*/ + if(!player.isGodMode() && !zone.canJoin(player)) { + player.notify("You do not belong to that world."); + return; + } player.changeZone(zone); } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneSearchRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneSearchRequest.java index 50ff6d01..c9863a41 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneSearchRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneSearchRequest.java @@ -1,19 +1,20 @@ package brainwine.gameserver.server.requests; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; +import java.util.Collection; +import java.util.Comparator; import java.util.List; -import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Predicate; +import java.util.stream.Collectors; import brainwine.gameserver.GameServer; import brainwine.gameserver.player.Player; import brainwine.gameserver.server.PlayerRequest; import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.server.messages.ZoneSearchMessage; +import brainwine.gameserver.server.models.ZoneSearchData; import brainwine.gameserver.zone.Biome; import brainwine.gameserver.zone.Zone; -import brainwine.gameserver.zone.ZoneManager; @RequestInfo(id = 23) public class ZoneSearchRequest extends PlayerRequest { @@ -22,65 +23,63 @@ public class ZoneSearchRequest extends PlayerRequest { @Override public void process(Player player) { - List result = new ArrayList<>(); - List zones = searchZones(GameServer.getInstance().getZoneManager()); - zones.remove(player.getZone()); - Collections.shuffle(zones); - Set indices = new HashSet<>(); - int index = (int)(Math.random() * zones.size()); - int amount = Math.min(9, zones.size()); + Collection allZones = GameServer.getInstance().getZoneManager().getZones(); + int searchLimit = 200; + int displayLimit = 10; - for(int i = 0; i < amount; i++) { - while(indices.contains(index)) { - index = (int)(Math.random() * zones.size()); - } - - indices.add(index); - result.add(zones.get(i)); - } + // Find zones matching the filter + List zones = allZones.stream() + .filter(getFilter(player).and(zone -> zone != player.getZone())) + .limit(searchLimit) + .collect(Collectors.toList()); + + // Get random zones to display + List data = zones.stream() + .skip(ThreadLocalRandom.current().nextInt(Math.max(1, 1 + zones.size() - displayLimit))) + .limit(displayLimit) + .sorted(getComparator()) + .map(zone -> new ZoneSearchData(zone, player)) + .collect(Collectors.toList()); - player.sendDelayedMessage(new ZoneSearchMessage(type, result, 0)); + // Send data + player.sendDelayedMessage(new ZoneSearchMessage(type, data, 0)); } - private List searchZones(ZoneManager manager) { - List zones = new ArrayList<>(); - + private Predicate getFilter(Player player) { + switch(type) { + case "Random": + return zone -> zone.isPublic(); + case "Popular": + return zone -> zone.isPublic() && zone.isPopular(); + case "Unexplored": + return zone -> zone.isPublic() && !zone.isProtected() && zone.isUnexplored(); + case "Owned": + return zone -> zone.isOwner(player); + case "Member": + return zone -> zone.isMember(player); + case "PvP": + return zone -> zone.isPublic() && zone.isPvp(); + case "Plain": + case "Hell": + case "Arctic": + case "Desert": + case "Brain": + case "Deep": + case "Space": + return zone -> zone.isPublic() && !zone.isProtected() && zone.getBiome() == Biome.valueOf(type.toUpperCase()); + default: + return zone -> zone.isPublic() && zone.getName().toLowerCase().contains(type.toLowerCase()); + } + } + + private Comparator getComparator() { switch(type) { - case "Random": - zones.addAll(manager.searchZones(null, null)); - break; - case "Plain": - zones.addAll(manager.searchZones(zone -> zone.getBiome() == Biome.PLAIN)); - break; - case "Arctic": - zones.addAll(manager.searchZones(zone -> zone.getBiome() == Biome.ARCTIC)); - break; - case "Hell": - zones.addAll(manager.searchZones(zone -> zone.getBiome() == Biome.HELL)); - break; - case "Desert": - zones.addAll(manager.searchZones(zone -> zone.getBiome() == Biome.DESERT)); - break; - case "Brain": - zones.addAll(manager.searchZones(zone -> zone.getBiome() == Biome.BRAIN)); - break; - case "Deep": - zones.addAll(manager.searchZones(zone -> zone.getBiome() == Biome.DEEP)); - break; - case "Space": - zones.addAll(manager.searchZones(zone -> zone.getBiome() == Biome.SPACE)); - break; - case "Unexplored": - zones.addAll(manager.searchZones(Zone::isUnexplored, (a, b) -> Float.compare(a.getExplorationProgress(), b.getExplorationProgress()))); - break; case "Popular": - zones.addAll(manager.searchZones(Zone::isPopular, (a, b) -> Integer.compare(b.getPlayers().size(), a.getPlayers().size()))); - break; + return (a, b) -> Integer.compare(b.getPlayerCount(), a.getPlayerCount()); // Most players first + case "Unexplored": + return (a, b) -> Integer.compare(a.getChunksExploredCount(), b.getChunksExploredCount()); // Least explored first default: - zones.addAll(manager.searchZones(zone -> zone.getName().toLowerCase().contains(type.toLowerCase()))); - break; + return (a, b) -> 0; } - - return zones; } } diff --git a/gameserver/src/main/java/brainwine/gameserver/shop/ItemProduct.java b/gameserver/src/main/java/brainwine/gameserver/shop/ItemProduct.java new file mode 100644 index 00000000..adadc637 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/shop/ItemProduct.java @@ -0,0 +1,58 @@ +package brainwine.gameserver.shop; + +import java.util.Collections; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import brainwine.gameserver.dialog.Dialog; +import brainwine.gameserver.dialog.DialogListItem; +import brainwine.gameserver.dialog.DialogSection; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.player.NotificationType; +import brainwine.gameserver.player.Player; + +public class ItemProduct extends Product { + + private final Map items; + + @JsonCreator + public ItemProduct( + @JsonProperty(value = "name", required = true) String name, + @JsonProperty(value = "cost", required = true) int cost, + @JsonProperty(value = "items", required = true) Map items) { + super(name, cost); + this.items = items; + } + + @Override + public void purchase(Player player) { + DialogSection section = new DialogSection().setTitle("You received:"); + Dialog dialog = new Dialog().addSection(section); + + // Add items to inventory + items.forEach((item, quantity) -> { + if(quantity <= 0) { + return; + } + + section.addItem(new DialogListItem() + .setItem(item.getCode()) + .setImage(String.format("inventory/%s", item.getId())) + .setText(String.format("%s x %s", item.getTitle(), quantity))); + player.getInventory().addItem(item, quantity, true); + }); + + // Show dialog + if(player.isV3()) { + player.showDialog(dialog); + } else { + player.notify(dialog, NotificationType.REWARD); + } + } + + public Map getItems() { + return Collections.unmodifiableMap(items); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/shop/Product.java b/gameserver/src/main/java/brainwine/gameserver/shop/Product.java new file mode 100644 index 00000000..97529639 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/shop/Product.java @@ -0,0 +1,50 @@ +package brainwine.gameserver.shop; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeInfo.As; +import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; + +import brainwine.gameserver.player.Player; + +@JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "type") +@JsonSubTypes({ + @Type(name = "item", value = ItemProduct.class), + @Type(name = "zone", value = ZoneProduct.class), +}) +public abstract class Product { + + protected final String name; + protected final int cost; + protected boolean available = true; + protected String description = "No description is available for this product."; + protected ProductImage image = new ProductImage("inventory/air"); + + public Product(String name, int cost) { + this.name = name; + this.cost = cost; + } + + public abstract void purchase(Player player); + + public String getName() { + return name; + } + + public int getCost() { + return cost; + } + + public boolean isAvailable() { + return available; + } + + public String getDescription() { + return description; + } + + public ProductImage getImage() { + return image; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/shop/ProductImage.java b/gameserver/src/main/java/brainwine/gameserver/shop/ProductImage.java new file mode 100644 index 00000000..7e46b0c0 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/shop/ProductImage.java @@ -0,0 +1,59 @@ +package brainwine.gameserver.shop; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; + +public class ProductImage { + + private final Layer[] layers; + + @JsonCreator + public ProductImage(Layer[] layers) { + this.layers = layers; + } + + @JsonCreator + public ProductImage(String sprite) { + layers = new Layer[1]; + layers[0] = new Layer(sprite, null); + } + + public boolean isLayered() { + return layers.length > 1; + } + + public String getBaseSprite() { + return layers.length == 0 ? "inventory/air" : layers[0].getSprite(); + } + + @JsonValue + private Layer[] getJsonValue() { + return layers; + } + + private static class Layer { + + private final String sprite; + private final String color; + + @JsonCreator + public Layer( + @JsonProperty(value = "sprite", required = true) String sprite, + @JsonProperty(value = "color") String color) { + this.sprite = sprite; + this.color = color; + } + + @JsonGetter("frame") + public String getSprite() { + return sprite; + } + + @SuppressWarnings("unused") + public String getColor() { + return color; + } + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/shop/ShopManager.java b/gameserver/src/main/java/brainwine/gameserver/shop/ShopManager.java new file mode 100644 index 00000000..c530aaaa --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/shop/ShopManager.java @@ -0,0 +1,126 @@ +package brainwine.gameserver.shop; + +import static brainwine.shared.LogMarkers.SERVER_MARKER; + +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.fasterxml.jackson.core.type.TypeReference; + +import brainwine.gameserver.GameConfiguration; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.resource.ResourceFinder; +import brainwine.gameserver.server.messages.StatMessage; +import brainwine.gameserver.server.models.PlayerStat; +import brainwine.gameserver.util.MapHelper; +import brainwine.shared.JsonHelper; + +/** + * Manages the Crown Store. + * Since no real money is involved, we can afford to be pretty careless with its implementation. + */ +public class ShopManager { + + private static final Logger logger = LogManager.getLogger(); + private static final Map sections = new LinkedHashMap<>(); + private static final Map products = new HashMap<>(); + + public static void loadShopData() { + logger.info(SERVER_MARKER, "Loading shop data ..."); + sections.clear(); + products.clear(); + + try { + URL url = ResourceFinder.getResourceUrl("shop.json"); + Map data = JsonHelper.readValue(url, new TypeReference>(){}); + sections.putAll(JsonHelper.readValue(data.getOrDefault("sections", Collections.emptyMap()), new TypeReference>(){})); + products.putAll(JsonHelper.readValue(data.getOrDefault("products", Collections.emptyMap()), new TypeReference>(){})); + } catch(Exception e) { + logger.error(SERVER_MARKER, "Could not load shop data", e); + return; + } + + try { + // Create section data + for(Entry entry : sections.entrySet()) { + Map data = JsonHelper.readValue(entry.getValue(), new TypeReference>(){}); + data.put("key", entry.getKey()); + data.put("items", data.remove("products")); + MapHelper.appendList(GameConfiguration.getBaseConfig(), "shop.sections", data); + } + + // Create product data + for(Entry entry : products.entrySet()) { + Product product = entry.getValue(); + ProductImage image = product.getImage(); + + // Skip product if it isn't available + if(!product.isAvailable()) { + continue; + } + + Map data = JsonHelper.readValue(product, new TypeReference>(){}); + data.put("key", entry.getKey()); + + // Convert inventory data + if(data.containsKey("items")) { + Map inventoryData = (Map)data.remove("items"); // TODO this is kinda shit + inventoryData.forEach((item, quantity) -> MapHelper.appendList(data, "inventory", Arrays.asList(item, quantity))); + } + + // Convert image data + if(image != null && data.containsKey("image")) { + if(image.isLayered()) { + data.put("images", data.remove("image")); + } + + data.put("image", image.getBaseSprite()); + } + + MapHelper.appendList(GameConfiguration.getBaseConfig(), "shop.items", data); + } + } catch(Exception e) { + logger.error(SERVER_MARKER, "An error occured while converting shop data", e); + products.clear(); // Clear products so purchases can't be made + return; + } + + logger.info(SERVER_MARKER, "Successfully loaded {} product{}", products.size(), products.size() == 1 ? "" : "s"); + } + + public static boolean purchaseProduct(Player player, Product product) { + // Check if item is available + if(product == null || !product.isAvailable()) { + player.notify("Oops! There was an error with your purchase."); + player.sendMessage(new StatMessage(PlayerStat.CROWNS, player.getCrowns())); + return false; + } + + // Check if player has enough crowns + if(player.getCrowns() < product.getCost()) { + player.notify("You do not have enough crowns to buy this."); + player.sendMessage(new StatMessage(PlayerStat.CROWNS, player.getCrowns())); + return false; + } + + player.setCrowns(player.getCrowns() - product.getCost()); + product.purchase(player); + return true; + } + + public static boolean purchaseProduct(Player player, String productKey) { + return purchaseProduct(player, products.get(productKey)); + } + + public static Product getProduct(String key) { + return products.get(key); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/shop/ShopSection.java b/gameserver/src/main/java/brainwine/gameserver/shop/ShopSection.java new file mode 100644 index 00000000..6dc57511 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/shop/ShopSection.java @@ -0,0 +1,33 @@ +package brainwine.gameserver.shop; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ShopSection { + + private final String name; + private final String icon; + private final String[] products; + + @JsonCreator + public ShopSection( + @JsonProperty(value = "name", required = true) String name, + @JsonProperty(value = "icon", required = true) String icon, + @JsonProperty(value = "products", required = true) String... products) { + this.name = name; + this.icon = icon; + this.products = products; + } + + public String getName() { + return name; + } + + public String getIcon() { + return icon; + } + + public String[] getProducts() { + return products; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/shop/ZoneProduct.java b/gameserver/src/main/java/brainwine/gameserver/shop/ZoneProduct.java new file mode 100644 index 00000000..e9a4eee7 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/shop/ZoneProduct.java @@ -0,0 +1,104 @@ +package brainwine.gameserver.shop; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.dialog.DialogHelper; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.Biome; +import brainwine.gameserver.zone.gen.ZoneGenerator; + +public class ZoneProduct extends Product { + + private final Settings settings; + + @JsonCreator + public ZoneProduct( + @JsonProperty(value = "name", required = true) String name, + @JsonProperty(value = "cost", required = true) int cost, + @JsonProperty(value = "zone", required = true) Settings settings) { + super(name, cost); + this.settings = settings; + } + + @Override + public void purchase(Player player) { + ZoneGenerator generator = settings.getGenerator() == null ? ZoneGenerator.getZoneGenerator(settings.getBiome()) : ZoneGenerator.getZoneGenerator(settings.getGenerator()); + + // Refund purchase if generator doesn't exist + if(generator == null) { + player.notify("Oops! There was a problem with your purchase."); + player.setCrowns(player.getCrowns() + cost); + return; + } + + player.showDialog(DialogHelper.messageDialog("World purchased!", "We've started generating your private world. We'll let you know when it's ready for exploration!")); + generator.generateZoneAsync(settings.getBiome(), settings.getWidth(), settings.getHeight(), zone -> { + // Refund purchase if world failed to generate + if(zone == null) { + player.showDialog(DialogHelper.messageDialog("There was a problem generating your private world. Your crowns have been refunded.")); + player.setCrowns(player.getCrowns() + cost); + return; + } + + // Update world ownership + zone.setOwner(player); + zone.setPrivate(true); + zone.setProtected(true); + GameServer.getInstance().getZoneManager().addZone(zone); + + // Ask player if they want to travel to their newly purchased world + player.showDialog(DialogHelper.messageDialog("Your private world is ready!", String.format( + "Your private world '%s' is ready for exploration and adventure! Let's head there now.", zone.getName())).setActions("yesno"), input -> { + // Check cancellation + if(input.length == 1 && "cancel".equals(input[0])) { + return; + } + + // Send player to purchased zone + player.changeZone(zone); + }); + }); + } + + /** + * Zone generator settings for the product. + */ + private static class Settings { + + private final Biome biome; + private final int width; + private final int height; + private final String generator; + + @JsonCreator + public Settings( + @JsonProperty(value = "biome", required = true) Biome biome, + @JsonProperty(value = "width", required = true) int width, + @JsonProperty(value = "height", required = true) int height, + @JsonProperty(value = "generator") String generator) { + this.biome = biome; + this.width = width; + this.height = height; + this.generator = generator; + } + + public Biome getBiome() { + return biome; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public String getGenerator() { + return generator; + } + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index a2c16ca6..93ab3de0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -2,6 +2,7 @@ import java.io.File; import java.time.OffsetDateTime; +import java.time.temporal.TemporalUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -80,6 +81,10 @@ public class Zone { private float time = (float)Math.random(); // TODO temporary private float temperature; private float acidity; + private boolean isPrivate; + private boolean isProtected; + private boolean pvp; + private String owner; private final ChunkManager chunkManager; private final SteamManager steamManager; private final GrowthManager growthManager; @@ -87,14 +92,16 @@ public class Zone { private final EntityManager entityManager = new EntityManager(this); private final LiquidManager liquidManager = new LiquidManager(this); private final MachineManager machineManager = new MachineManager(this); + private final Set pendingSunlight = new HashSet<>(); + private final List members = new ArrayList<>(); private final List blockChanges = new ArrayList<>(); private final List> blockTimers = new ArrayList<>(); - private final Set pendingSunlight = new HashSet<>(); private final Map dungeons = new HashMap<>(); private final Map metaBlocks = new HashMap<>(); private final Map globalMetaBlocks = new HashMap<>(); private final Map fieldBlocks = new HashMap<>(); private final Map damageFieldBlocks = new HashMap<>(); + private final Map actionHistory = new HashMap<>(); private long lastStatusUpdate = System.currentTimeMillis(); private int ticksElapsed; private boolean modified; @@ -113,7 +120,12 @@ protected Zone(String documentId, ZoneConfigFile config, ZoneDataFile data) { steamManager.setData(data.getSteamData()); machineManager.loadData(config); pendingSunlight.addAll(data.getPendingSunlight()); + owner = config.getOwner(); + members.addAll(config.getMembers()); + actionHistory.putAll(config.getActionHistory()); acidity = biome == Biome.ARCTIC || biome == Biome.SPACE ? 0 : config.getAcidity(); + isPrivate = config.isPrivate(); + isProtected = config.isProtected(); creationDate = config.getCreationDate(); } @@ -284,6 +296,12 @@ public void spawnEffect(float x, float y, String type, Object data) { sendLocalMessage(new EffectMessage(x, y, type, data), x, y); } + public void kickAllPlayers(String reason, boolean shouldReconnect) { + for(Player player : getPlayers()) { + player.kick(reason, shouldReconnect); + } + } + public boolean isPointVisibleFrom(int x1, int y1, int x2, int y2) { return raycast(x1, y1, x2, y2) == null; } @@ -594,6 +612,12 @@ public boolean isBlockProtected(int x, int y, Player player) { } public boolean isBlockProtected(int x, int y, Player player, Collection fieldBlocks) { + // Check protection at zone level + if(player != null && isProtected(player)) { + return true; + } + + // Check field blocks for(MetaBlock fieldBlock : fieldBlocks) { Item item = fieldBlock.getItem(); int fX = fieldBlock.getX(); @@ -1332,6 +1356,18 @@ public MachineManager getMachineManager() { return machineManager; } + public void recordActionTime(String name) { + actionHistory.put(name.toLowerCase(), OffsetDateTime.now()); + } + + public boolean isActionOnCooldown(String name, long cooldown, TemporalUnit unit) { + return actionHistory.containsKey(name.toLowerCase()) && !OffsetDateTime.now().isAfter(actionHistory.get(name.toLowerCase()).plus(cooldown, unit)); + } + + public Map getActionHistory() { + return Collections.unmodifiableMap(actionHistory); + } + /** * @return The specified coordinates in a player-readable format * For example, {@code x: 200 y: 300} in a plain biome becomes {@code 800 west, 100 below} @@ -1590,8 +1626,13 @@ public int getSeed() { return (int)(UUID.fromString(documentId).getMostSignificantBits() >> 32); } + /** + * @deprecated DO NOT CALL DIRECTLY. + * If you have to rename a zone, please use {@link ZoneManager#renameZone(Zone, String)}. + */ public void setName(String name) { this.name = name; + kickAllPlayers("Zone name changed.", true); } public String getName() { @@ -1646,6 +1687,97 @@ public float getAcidity() { return acidity; } + public void setPrivate(boolean value) { + this.isPrivate = value; + kickAllPlayers("Accessibility status changed.", true); // The login handler will kick non-members out of the zone if the world is made private + } + + public boolean canJoin(Player player) { + return isPublic() || isOwner(player) || isMember(player); + } + + public boolean isPublic() { + return !isPrivate(); + } + + public boolean isPrivate() { + return isPrivate; + } + + public void setProtected(boolean value) { + this.isProtected = value; + kickAllPlayers("Protection status changed.", true); + } + + public boolean isProtected(Player player) { + return isProtected && !isOwner(player) && !isMember(player); + } + + public boolean isProtected() { + return isProtected; + } + + public void setPvp(boolean pvp) { + this.pvp = pvp; + kickAllPlayers("PvP status changed.", true); + } + + public boolean isPvp() { + return pvp; + } + + public boolean isOwner(Player player) { + return isOwned() && player.getDocumentId().equals(owner); + } + + public boolean isOwned() { + return owner != null; + } + + public void setOwner(Player player) { + this.owner = player.getDocumentId(); + + // Update spawn teleporter ownership + for(MetaBlock block : getMetaBlocksWithItem("mechanical/zone-teleporter")) { + block.setOwner(owner); + sendBlockMetaUpdate(block); + } + } + + public String getOwner() { + return owner; + } + + public void addMember(Player player) { + members.add(player.getDocumentId()); + + // Force player to reconnect if they're currently in this zone + if(player.getZone() == this) { + player.kick("Member status changed.", true); + } + } + + public void removeMember(Player player) { + members.remove(player.getDocumentId()); + + // Kick the player from the world if they are currently in it or force them to reconnect if the world is public + if(player.getZone() == this) { + if(isPublic()) { + player.kick("Member status changed.", true); + } else { + player.changeZone(null); + } + } + } + + public boolean isMember(Player player) { + return members.contains(player.getDocumentId()); + } + + public List getMembers() { + return Collections.unmodifiableList(members); + } + public OffsetDateTime getCreationDate() { return creationDate; } @@ -1683,6 +1815,12 @@ public Map getClientConfig(Player player) { config.put("surface", surface); config.put("chunks_explored", chunksExplored); config.put("chunks_explored_count", getChunksExploredCount()); + config.put("private", isPrivate); + config.put("protected", isProtected(player)); + config.put("protected_player", isProtected(player)); + config.put("owner", isOwner(player)); + config.put("member", isMember(player)); + config.put("pvp", pvp); Map depth = new HashMap<>(); List earth = new ArrayList<>(); diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java index 0bbe0080..cda07604 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java @@ -1,6 +1,7 @@ package brainwine.gameserver.zone; import java.time.OffsetDateTime; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -31,30 +32,30 @@ public class ZoneConfigFile { @JsonSetter(nulls = Nulls.SKIP) private float acidity = 1.0F; + @JsonSetter(value = "private") + private boolean isPrivate; + + @JsonSetter(value = "protected") + private boolean isProtected; + + @JsonSetter(nulls = Nulls.SKIP) + private String owner; + + @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) + private List members = new ArrayList<>(); + @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) private Map> discoveredParts = new HashMap<>(); + @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) + private Map actionHistory = new HashMap<>(); + @JsonSetter(nulls = Nulls.SKIP) private OffsetDateTime creationDate = OffsetDateTime.now(); @JsonSetter(nulls = Nulls.SKIP) private OffsetDateTime lastActiveDate = OffsetDateTime.now(); - public ZoneConfigFile(Zone zone) { - this(zone.getName(), zone.getBiome(), zone.getWidth(), zone.getHeight(), zone.getAcidity(), zone.getDiscoveredParts(), zone.getCreationDate()); - } - - public ZoneConfigFile(String name, Biome biome, int width, int height, float acidity, - Map> discoveredParts, OffsetDateTime creationDate) { - this.name = name; - this.biome = biome; - this.width = width; - this.height = height; - this.acidity = acidity; - this.discoveredParts = discoveredParts; - this.creationDate = creationDate; - } - @JsonCreator private ZoneConfigFile(@JsonProperty(value = "name", required = true) String name, @JsonProperty(value = "width", required = true) int width, @@ -64,6 +65,21 @@ private ZoneConfigFile(@JsonProperty(value = "name", required = true) String nam this.height = height; } + public ZoneConfigFile(Zone zone) { + this.name = zone.getName(); + this.biome = zone.getBiome(); + this.width = zone.getWidth(); + this.height = zone.getHeight(); + this.acidity = zone.getAcidity(); + this.isPrivate = zone.isPrivate(); + this.isProtected = zone.isProtected(); + this.owner = zone.getOwner(); + this.members = zone.getMembers(); + this.discoveredParts = zone.getDiscoveredParts(); + this.actionHistory = zone.getActionHistory(); + this.creationDate = zone.getCreationDate(); + } + public String getName() { return name; } @@ -84,10 +100,30 @@ public float getAcidity() { return acidity; } + public boolean isPrivate() { + return isPrivate; + } + + public boolean isProtected() { + return isProtected; + } + + public String getOwner() { + return owner; + } + + public List getMembers() { + return members; + } + public Map> getDiscoveredParts() { return discoveredParts; } + public Map getActionHistory() { + return actionHistory; + } + public OffsetDateTime getCreationDate() { return creationDate; } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java index f695aee6..52a318e7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java @@ -192,29 +192,58 @@ public void addZone(Zone zone) { zonesByName.put(name.toLowerCase(), zone); } - /**Should the game create a new world because all the worlds are established? - * - * @return true iff the game should create a new world at next opportunity + /** + * Should the automatic zone generator generate a new zone? + * TODO could be slow, it might be a better idea to just check the most recently auto-generated zone instead. + * + * @return {@code true} if all unowned worlds are at least 40% explored, otherwise {@code false}. */ public boolean shouldGenerateUnexploredZone() { - return getZones().stream().allMatch(zone -> zone.getExplorationProgress() >= 0.4); + return getZones().stream().filter(zone -> !zone.isOwned()).allMatch(zone -> zone.getExplorationProgress() >= 0.4); + } + + /** + * Renames the specified zone and re-indexes it. + * + * @return {@code true} if the renaming was successful, otherwise {@code false}. + */ + @SuppressWarnings("deprecation") + public boolean renameZone(Zone zone, String name) { + if(doesZoneExist(name)) { + return false; // Return false if name is already taken + } + + if(!zonesByName.remove(zone.getName().toLowerCase(), zone)) { + return false; // Sanity check + } + + zone.setName(name); + zonesByName.put(name.toLowerCase(), zone); + return true; } public Zone getZone(String id) { return zones.get(id); } - public Zone getZoneByName(String name) { - return zonesByName.get(name.toLowerCase()); + public boolean doesZoneExist(String name) { + return zonesByName.containsKey(name.toLowerCase()); } - public Zone getRandomZone() { - return getRandomZone(null); + public Zone getZoneByName(String name) { + return zonesByName.get(name.toLowerCase()); } - public Zone getRandomZone(Predicate predicate) { - List zones = searchZones(predicate); - return zones.get((int)(Math.random() * zones.size())); + /** + * @return A public, non-owned, recently-generated temperate world (with players if possible) or {@code null} if no such world exists. + */ + public Zone findBeginnerZone() { + return zones.values().stream() + .filter(zone -> zone.isPublic() && !zone.isOwned() && zone.isUnexplored() && zone.getBiome() == Biome.PLAIN) + .sorted((a, b) -> b.getCreationDate().compareTo(a.getCreationDate())) + .limit(50) + .sorted((a, b) -> Integer.compare(b.getPlayerCount(), a.getPlayerCount())) + .findFirst().orElse(null); } public List searchZones(Predicate predicate) { diff --git a/gameserver/src/main/resources/shop.json b/gameserver/src/main/resources/shop.json new file mode 100644 index 00000000..de163030 --- /dev/null +++ b/gameserver/src/main/resources/shop.json @@ -0,0 +1,460 @@ +{ + "sections": { + "utilities": { + "name": "Utilities", + "icon": "icon-profile-equipment", + "products": [ + "brain-wine", + "skill-book", + "skill-reset", + "name-change" + ] + }, + "protectors": { + "name": "Protectors", + "icon": "icon-skill-automata", + "products": [ + "giga-protector", + "mega-protector", + "large-protector", + "small-protector", + "micro-protector-pack" + ] + }, + "worlds": { + "name": "Private Worlds", + "icon": "icon-world", + "products": [ + "private-world-small", + "private-world", + "private-world-large", + "private-world-hell-small", + "private-world-hell", + "private-world-hell-large", + "private-world-arctic-small", + "private-world-arctic", + "private-world-arctic-large", + "private-world-desert-small", + "private-world-desert", + "private-world-desert-large", + "private-world-deep-small", + "private-world-deep", + "private-world-deep-large", + "private-world-brain-small", + "private-world-brain", + "private-world-brain-large", + "private-world-space-small", + "private-world-space", + "private-world-space-large" + ] + } + }, + "products": { + "brain-wine": { + "type": "item", + "name": "Brain Wine", + "cost": 75, + "description": "Upgrade any skill by one point with this carefully brewed essence of brain! Note: each skill can only be upgraded once via brain wine, and only to Level 10.", + "image": [ + { "sprite": "inventory/consumables/brain-wine" }, + { "sprite": "shop/banner", "color": "AAFF66" }, + { "sprite": "shop/banner-text-sale" } + ], + "items": { + "consumables/brain-wine": 1 + } + }, + "skill-book": { + "type": "item", + "name": "Skill Book", + "cost": 75, + "description": "Upgrade any skill by one point with this lovely tome of knowledge! Note: each skill can only be upgraded once via books, and only to Level 10.", + "image": [ + { "sprite": "inventory/consumables/book-skill" }, + { "sprite": "shop/banner", "color": "AAFF66" }, + { "sprite": "shop/banner-text-sale" } + ], + "items": { + "consumables/book-skill": 1 + } + }, + "skill-reset": { + "type": "item", + "name": "Skill Reset", + "cost": 100, + "description": "Go back in time a little with this handy skill reset! Reclaim all of your skill points and re-apply them however you like.", + "image": "inventory/consumables/skill-reset", + "items": { + "consumables/skill-reset": 1 + } + }, + "name-change": { + "type": "item", + "name": "Name Change", + "cost": 125, + "description": "Want to change your player name? Grab this handy name tag and get creative!", + "image": "inventory/consumables/name-change", + "items": { + "consumables/name-change": 1 + } + }, + "giga-protector": { + "type": "item", + "name": "Giga Protector", + "cost": 225, + "description": "Protect your creations and valuables from being mined by other players with this giga protector! When placed, it will emit a protective field that protects blocks within a 120 block radius.", + "image": "inventory/mechanical/dish-giga", + "items": { + "mechanical/dish-giga": 1 + } + }, + "mega-protector": { + "type": "item", + "name": "Mega Protector", + "cost": 125, + "description": "Protect your creations and valuables from being mined by other players with this mega protector! When placed, it will emit a protective field that protects blocks within a 60 block radius.", + "image": "inventory/mechanical/dish-mega", + "items": { + "mechanical/dish-mega": 1 + } + }, + "large-protector": { + "type": "item", + "name": "Large Protector", + "cost": 50, + "description": "Protect your creations and valuables from being mined by other players with this large protector! When placed, it will emit a protective field that protects blocks within a 30 block radius.", + "image": "inventory/mechanical/dish-large", + "items": { + "mechanical/dish-large": 1 + } + }, + "small-protector": { + "type": "item", + "name": "Small Protector", + "cost": 20, + "description": "Protect your creations and valuables from being mined by other players with this small protector! When placed, it will emit a protective field that protects blocks within a 15 block radius.", + "image": "inventory/mechanical/dish", + "items": { + "mechanical/dish": 1 + } + }, + "micro-protector-pack": { + "type": "item", + "name": "Micro Protector Pack", + "cost": 20, + "description": "Protect your creations and valuables from being mined by other players with these micro protectors! When placed, each dish will emit a protective field that protects blocks within a 5 block radius.", + "image": "inventory/mechanical/dish-micro", + "items": { + "mechanical/dish-micro": 4 + } + }, + "private-world-small": { + "type": "zone", + "name": "Private Mini World", + "cost": 250, + "description": "Claim your own untouched world! Get all the resources, loot, and exploration you'll ever need. You can even invite friends to come visit and settle with you... or just keep it all to yourself!", + "image": [ + { "sprite": "shop/biome-normal" }, + { "sprite": "shop/banner", "color": "AAFF66" }, + { "sprite": "shop/banner-text-mini" } + ], + "zone": { + "biome": "plain", + "width": 800, + "height": 400 + } + }, + "private-world": { + "type": "zone", + "name": "Private World", + "cost": 500, + "description": "Claim your own untouched world! Get all the resources, loot, and exploration you'll ever need. You can even invite friends to come visit and settle with you... or just keep it all to yourself!", + "image": "shop/biome-normal", + "zone": { + "biome": "plain", + "width": 2000, + "height": 600 + } + }, + "private-world-large": { + "type": "zone", + "name": "Private Large World", + "cost": 1000, + "description": "Claim your own untouched world! Get all the resources, loot, and exploration you'll ever need. You can even invite friends to come visit and settle with you... or just keep it all to yourself!", + "image": [ + { "sprite": "shop/biome-normal" }, + { "sprite": "shop/banner", "color": "AAFF66" }, + { "sprite": "shop/banner-text-xl" } + ], + "zone": { + "biome": "plain", + "width": 3000, + "height": 800 + } + }, + "private-world-hell-small": { + "type": "zone", + "name": "Private Mini Hell World", + "cost": 250, + "description": "Claim your own untouched Hell world! Get your own expiator, plus a fresh bevy of fire salt, bloodstone, and a never-ending supply of ghostly ectoplasm.", + "image": [ + { "sprite": "shop/biome-hell" }, + { "sprite": "shop/banner", "color": "FF3311" }, + { "sprite": "shop/banner-text-mini" } + ], + "zone": { + "biome": "hell", + "width": 800, + "height": 400 + } + }, + "private-world-hell": { + "type": "zone", + "name": "Private Hell World", + "cost": 500, + "description": "Claim your own untouched Hell world! Get your own expiator, plus a fresh bevy of fire salt, bloodstone, and a never-ending supply of ghostly ectoplasm.", + "image": "shop/biome-hell", + "zone": { + "biome": "hell", + "width": 2000, + "height": 600 + } + }, + "private-world-hell-large": { + "type": "zone", + "name": "Private Large Hell World", + "cost": 1000, + "description": "Claim your own untouched Hell world! Get your own expiator, plus a fresh bevy of fire salt, bloodstone, and a never-ending supply of ghostly ectoplasm.", + "image": [ + { "sprite": "shop/biome-hell" }, + { "sprite": "shop/banner", "color": "FF3311" }, + { "sprite": "shop/banner-text-xl" } + ], + "zone": { + "biome": "hell", + "width": 3000, + "height": 800 + } + }, + "private-world-arctic-small": { + "type": "zone", + "name": "Private Mini Arctic World", + "cost": 250, + "description": "Claim your own untouched Arctic world! Discover sparkly diamonds, ice sculptures, and maybe even the rare jackalope head. Also includes all the snow and ice you'll ever need!", + "image": [ + { "sprite": "shop/biome-arctic" }, + { "sprite": "shop/banner", "color": "66AAFF" }, + { "sprite": "shop/banner-text-mini" } + ], + "zone": { + "biome": "arctic", + "width": 800, + "height": 400 + } + }, + "private-world-arctic": { + "type": "zone", + "name": "Private Arctic World", + "cost": 500, + "description": "Claim your own untouched Arctic world! Discover sparkly diamonds, ice sculptures, and maybe even the rare jackalope head. Also includes all the snow and ice you'll ever need!", + "image": "shop/biome-arctic", + "zone": { + "biome": "arctic", + "width": 2000, + "height": 600 + } + }, + "private-world-arctic-large": { + "type": "zone", + "name": "Private Large Arctic World", + "cost": 1000, + "description": "Claim your own untouched Arctic world! Discover sparkly diamonds, ice sculptures, and maybe even the rare jackalope head. Also includes all the snow and ice you'll ever need!", + "image": [ + { "sprite": "shop/biome-arctic" }, + { "sprite": "shop/banner", "color": "66AAFF" }, + { "sprite": "shop/banner-text-xl" } + ], + "zone": { + "biome": "arctic", + "width": 3000, + "height": 800 + } + }, + "private-world-desert-small": { + "type": "zone", + "name": "Private Mini Desert World", + "cost": 250, + "description": "Claim your own untouched Desert world! Get a fresh supply of beryllium ore, tumbleweeds, and cacti. You'll also find a steady supply of armadillos, scorpions, and sandworms to farm!", + "image": [ + { "sprite": "shop/biome-desert" }, + { "sprite": "shop/banner", "color": "FFDD77" }, + { "sprite": "shop/banner-text-mini" } + ], + "zone": { + "biome": "desert", + "width": 800, + "height": 400 + } + }, + "private-world-desert": { + "type": "zone", + "name": "Private Desert World", + "cost": 500, + "description": "Claim your own untouched Desert world! Get a fresh supply of beryllium ore, tumbleweeds, and cacti. You'll also find a steady supply of armadillos, scorpions, and sandworms to farm!", + "image": "shop/biome-desert", + "zone": { + "biome": "desert", + "width": 2000, + "height": 600 + } + }, + "private-world-desert-large": { + "type": "zone", + "name": "Private Large Desert World", + "cost": 1000, + "description": "Claim your own untouched Desert world! Get a fresh supply of beryllium ore, tumbleweeds, and cacti. You'll also find a steady supply of armadillos, scorpions, and sandworms to farm!", + "image": [ + { "sprite": "shop/biome-desert" }, + { "sprite": "shop/banner", "color": "FFDD77" }, + { "sprite": "shop/banner-text-xl" } + ], + "zone": { + "biome": "desert", + "width": 3000, + "height": 800 + } + }, + "private-world-deep-small": { + "type": "zone", + "name": "Private Mini Deep World", + "cost": 250, + "description": "Claim your own untouched Deep world! Find the elusive orange crystal along with an above-average amount of dungeons to raid! Just watch out for those explosive acid pipes!", + "image": [ + { "sprite": "shop/biome-deep" }, + { "sprite": "shop/banner", "color": "44334A" }, + { "sprite": "shop/banner-text-mini" } + ], + "zone": { + "biome": "deep", + "width": 600, + "height": 500 + } + }, + "private-world-deep": { + "type": "zone", + "name": "Private Deep World", + "cost": 500, + "description": "Claim your own untouched Deep world! Find the elusive orange crystal along with an above-average amount of dungeons to raid! Just watch out for those explosive acid pipes!", + "image": "shop/biome-deep", + "zone": { + "biome": "deep", + "width": 1200, + "height": 1000 + } + }, + "private-world-deep-large": { + "type": "zone", + "name": "Private Large Deep World", + "cost": 1000, + "description": "Claim your own untouched Deep world! Find the elusive orange crystal along with an above-average amount of dungeons to raid! Just watch out for those explosive acid pipes!", + "image": [ + { "sprite": "shop/biome-deep" }, + { "sprite": "shop/banner", "color": "44334A" }, + { "sprite": "shop/banner-text-xl" } + ], + "zone": { + "biome": "deep", + "width": 1800, + "height": 1500 + } + }, + "private-world-brain-small": { + "type": "zone", + "name": "Private Mini Brain World", + "cost": 250, + "description": "Claim your own untouched Brain world! Perfect for the thrill seeker, you'll find an insane amount of brains, green crystals, and earn that coveted Insurrectionist achievement!", + "image": [ + { "sprite": "shop/biome-brain" }, + { "sprite": "shop/banner", "color": "FF3311" }, + { "sprite": "shop/banner-text-mini" } + ], + "zone": { + "biome": "brain", + "width": 800, + "height": 400 + } + }, + "private-world-brain": { + "type": "zone", + "name": "Private Brain World", + "cost": 500, + "description": "Claim your own untouched Brain world! Perfect for the thrill seeker, you'll find an insane amount of brains, green crystals, and earn that coveted Insurrectionist achievement!", + "image": "shop/biome-brain", + "zone": { + "biome": "brain", + "width": 2000, + "height": 600 + } + }, + "private-world-brain-large": { + "type": "zone", + "name": "Private Large Brain World", + "cost": 1000, + "description": "Claim your own untouched Brain world! Perfect for the thrill seeker, you'll find an insane amount of brains, green crystals, and earn that coveted Insurrectionist achievement!", + "image": [ + { "sprite": "shop/biome-brain" }, + { "sprite": "shop/banner", "color": "FF3311" }, + { "sprite": "shop/banner-text-xl" } + ], + "zone": { + "biome": "brain", + "width": 3000, + "height": 800 + } + }, + "private-world-space-small": { + "type": "zone", + "name": "Private Mini Space World", + "cost": 250, + "description": "Claim your own untouched SPACE world! This brand new biome has it all - rare platinum, white crystals, custom loots, supernovas, low gravity, and more!", + "image": [ + { "sprite": "shop/biome-space" }, + { "sprite": "shop/banner", "color": "1A1A1A" }, + { "sprite": "shop/banner-text-mini" } + ], + "zone": { + "biome": "space", + "width": 800, + "height": 400 + } + }, + "private-world-space": { + "type": "zone", + "name": "Private Space World", + "cost": 500, + "description": "Claim your own untouched SPACE world! This brand new biome has it all - rare platinum, white crystals, custom loots, supernovas, low gravity, and more!", + "image": "shop/biome-space", + "zone": { + "biome": "space", + "width": 2000, + "height": 600 + } + }, + "private-world-space-large": { + "type": "zone", + "name": "Private Large Space World", + "cost": 1000, + "description": "Claim your own untouched SPACE world! This brand new biome has it all - rare platinum, white crystals, custom loots, supernovas, low gravity, and more!", + "image": [ + { "sprite": "shop/biome-space" }, + { "sprite": "shop/banner", "color": "1A1A1A" }, + { "sprite": "shop/banner-text-xl" } + ], + "zone": { + "biome": "space", + "width": 3000, + "height": 800 + } + } + } +} diff --git a/src/main/java/brainwine/DirectDataFetcher.java b/src/main/java/brainwine/DirectDataFetcher.java index 8658a4b3..b47b5e89 100644 --- a/src/main/java/brainwine/DirectDataFetcher.java +++ b/src/main/java/brainwine/DirectDataFetcher.java @@ -79,14 +79,17 @@ private static ZoneInfo createZoneInfo(Zone zone) { return new ZoneInfo(zone.getName(), zone.getBiome().getId(), null, + zone.isPvp(), false, - false, - false, + zone.isPrivate(), + zone.isProtected(), zone.getPlayers().size(), zone.getWidth(), zone.getHeight(), zone.getSurface(), zone.getExplorationProgress(), - zone.getCreationDate()); + zone.getCreationDate(), + zone.getOwner(), + zone.getMembers()); } } From 0a13d5ed06e84c9b75c60069009e8cdf1f4c6ee3 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Fri, 20 Dec 2024 18:21:02 +0100 Subject: [PATCH 115/176] Players in god mode should always be able to enter any zone --- gameserver/src/main/java/brainwine/gameserver/zone/Zone.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 93ab3de0..67d0140c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -1693,7 +1693,7 @@ public void setPrivate(boolean value) { } public boolean canJoin(Player player) { - return isPublic() || isOwner(player) || isMember(player); + return player.isGodMode() || isPublic() || isOwner(player) || isMember(player); } public boolean isPublic() { From b2cd6797f450346177e758b75220f43ce55e821d Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Fri, 20 Dec 2024 22:58:03 +0100 Subject: [PATCH 116/176] Remove unused imports --- .../gameserver/server/requests/TransactionRequest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/TransactionRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/TransactionRequest.java index dc1e1507..d0bbb3ef 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/TransactionRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/TransactionRequest.java @@ -4,8 +4,6 @@ import brainwine.gameserver.server.PlayerRequest; import brainwine.gameserver.server.RequestInfo; import brainwine.gameserver.shop.ShopManager; -import brainwine.gameserver.server.messages.StatMessage; -import brainwine.gameserver.server.models.PlayerStat; @RequestInfo(id = 41) public class TransactionRequest extends PlayerRequest { From 00e9dcf8f9c46eed1f1e6fcf2992a7cd296cb244 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 21 Dec 2024 00:04:14 +0100 Subject: [PATCH 117/176] Improve (?) block update tracking --- .../server/messages/BlockChangeMessage.java | 6 +++++- .../gameserver/server/models/BlockChangeData.java | 5 +++-- .../main/java/brainwine/gameserver/zone/Zone.java | 12 ++++++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/BlockChangeMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/BlockChangeMessage.java index 1897c8d9..81f76b16 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/messages/BlockChangeMessage.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/BlockChangeMessage.java @@ -18,7 +18,11 @@ public BlockChangeMessage(Collection blockChanges) { this.blockChanges = blockChanges; } + public BlockChangeMessage(int x, int y, Layer layer, int entityId, Item item, int mod) { + this(Arrays.asList(new BlockChangeData(x, y, layer, entityId, item, mod))); + } + public BlockChangeMessage(int x, int y, Layer layer, Item item, int mod) { - this(Arrays.asList(new BlockChangeData(x, y, layer, item, mod))); + this(x, y, layer, 0, item, mod); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/models/BlockChangeData.java b/gameserver/src/main/java/brainwine/gameserver/server/models/BlockChangeData.java index 9723e2bd..436ae4f0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/models/BlockChangeData.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/models/BlockChangeData.java @@ -12,14 +12,15 @@ public class BlockChangeData { private final int x; private final int y; private final Layer layer; - private final int entityId = 0; + private final int entityId; private final Item item; private final int mod; - public BlockChangeData(int x, int y, Layer layer, Item item, int mod) { + public BlockChangeData(int x, int y, Layer layer, int entityId, Item item, int mod) { this.x = x; this.y = y; this.layer = layer; + this.entityId = entityId; this.item = item; this.mod = mod; } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 67d0140c..403c1e64 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -94,13 +94,13 @@ public class Zone { private final MachineManager machineManager = new MachineManager(this); private final Set pendingSunlight = new HashSet<>(); private final List members = new ArrayList<>(); - private final List blockChanges = new ArrayList<>(); private final List> blockTimers = new ArrayList<>(); private final Map dungeons = new HashMap<>(); private final Map metaBlocks = new HashMap<>(); private final Map globalMetaBlocks = new HashMap<>(); private final Map fieldBlocks = new HashMap<>(); private final Map damageFieldBlocks = new HashMap<>(); + private final Map blockChanges = new HashMap<>(); private final Map actionHistory = new HashMap<>(); private long lastStatusUpdate = System.currentTimeMillis(); private int ticksElapsed; @@ -190,7 +190,7 @@ public void tick(float deltaTime) { // Send block changes to players who they are relevant to if(!blockChanges.isEmpty()) { for(Player player : getPlayers()) { - List blockChangesNearPlayer = blockChanges.stream() + List blockChangesNearPlayer = blockChanges.values().stream() .filter(blockChange -> player.isChunkActive(blockChange.getX(), blockChange.getY())) .collect(Collectors.toList()); @@ -1025,7 +1025,9 @@ public void updateBlock(int x, int y, Layer layer, Item item, int mod, Player ow // Queue block update if there are players in this zone. // TODO maybe check if the block update was in an active chunk, too? if(!getPlayers().isEmpty()) { - blockChanges.add(new BlockChangeData(x, y, layer, item, mod)); + int z = layer.ordinal(); + int changeIndex = z * width * height + getBlockIndex(x, y); + blockChanges.put(changeIndex, new BlockChangeData(x, y, layer, owner == null ? 0 : owner.getId(), item, mod)); } if(layer == Layer.FRONT) { @@ -1072,7 +1074,9 @@ public void updateBlockMod(int x, int y, Layer layer, int mod) { block.setMod(layer, mod); if(!getPlayers().isEmpty()) { - blockChanges.add(new BlockChangeData(x, y, layer, block.getItem(layer), mod)); + int z = layer.ordinal(); + int changeIndex = z * width * height + getBlockIndex(x, y); + blockChanges.put(changeIndex, new BlockChangeData(x, y, layer, 0, block.getItem(layer), mod)); } } From 99dcc66e5ca2c734198d45b20db5d307a545cc25 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 21 Dec 2024 00:10:13 +0100 Subject: [PATCH 118/176] Oops --- gameserver/src/main/java/brainwine/gameserver/zone/Zone.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 403c1e64..4efe1b77 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -1027,7 +1027,7 @@ public void updateBlock(int x, int y, Layer layer, Item item, int mod, Player ow if(!getPlayers().isEmpty()) { int z = layer.ordinal(); int changeIndex = z * width * height + getBlockIndex(x, y); - blockChanges.put(changeIndex, new BlockChangeData(x, y, layer, owner == null ? 0 : owner.getId(), item, mod)); + blockChanges.put(changeIndex, new BlockChangeData(x, y, layer, 0, item, mod)); // TODO entity id } if(layer == Layer.FRONT) { From c4ba9247454a534874e20033b88dc206fe6360f8 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 21 Dec 2024 19:44:21 +0100 Subject: [PATCH 119/176] Add world ownership & membership item placement checks --- .../main/java/brainwine/gameserver/item/Item.java | 14 ++++++++++++++ .../server/requests/BlockPlaceRequest.java | 10 ++++++++++ 2 files changed, 24 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/Item.java b/gameserver/src/main/java/brainwine/gameserver/item/Item.java index 24aadeb9..c0ee5fc9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/Item.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/Item.java @@ -129,6 +129,12 @@ public class Item { @JsonProperty("steam") private boolean steam; + @JsonProperty("ownership") + private boolean ownership; + + @JsonProperty("membership") + private boolean membership; + @JsonProperty("inventory") private LazyItemGetter inventoryItem; @@ -422,6 +428,14 @@ public boolean usesSteam() { return steam; } + public boolean requiresOwnership() { + return ownership; + } + + public boolean requiresMembership() { + return membership; + } + public Map getSkillBonuses() { return skillBonuses; } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java index c622dfc2..bb0570d5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java @@ -64,6 +64,16 @@ public void process(Player player) { return; } + if(!player.isGodMode() && item.requiresOwnership() && !zone.isOwner(player)) { + fail(player, "You can only place this in worlds you own."); + return; + } + + if(!player.isGodMode() && item.requiresMembership() && !zone.isOwner(player) && !zone.isMember(player)) { + fail(player, "You can only place these in owned or member worlds."); + return; + } + if(!player.isGodMode() && !item.canPlaceInField() && zone.isBlockProtected(x, y, player)) { fail(player, "This block is protected."); return; From a400e53f4c4025ba7fded60e94a9635e68f390c2 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 21 Dec 2024 23:44:51 +0100 Subject: [PATCH 120/176] Add block placement spacing restrictions --- .../java/brainwine/gameserver/item/Item.java | 33 +++++++++++++++++++ .../server/requests/BlockPlaceRequest.java | 12 +++++++ .../java/brainwine/gameserver/zone/Zone.java | 5 +++ 3 files changed, 50 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/Item.java b/gameserver/src/main/java/brainwine/gameserver/item/Item.java index c0ee5fc9..584a60b1 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/Item.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/Item.java @@ -78,6 +78,12 @@ public class Item { @JsonProperty("guard") private int guardLevel; + @JsonProperty("spacing") + private int spacing; + + @JsonProperty("spawn_spacing") + private int spawnSpacing; + @JsonProperty("power") private float power; @@ -186,6 +192,9 @@ public class Item { @JsonProperty("field_damage") private FieldDamage fieldDamage; + @JsonProperty("spacing_items") + private List spacingItems = new ArrayList<>(); + @JsonProperty("ingredients") private List craftingIngredients = new ArrayList<>(); @@ -360,6 +369,22 @@ public int getGuardLevel() { return guardLevel; } + public boolean hasSpacing() { + return spacing > 0; + } + + public int getSpacing() { + return spacing; + } + + public boolean hasSpawnSpacing() { + return spawnSpacing > 0; + } + + public int getSpawnSpacing() { + return spawnSpacing; + } + public float getPower() { return power; } @@ -548,6 +573,14 @@ public FieldDamage getFieldDamage() { return fieldDamage; } + public boolean hasSpacingItems() { + return !spacingItems.isEmpty(); + } + + public List getSpacingItems() { + return spacingItems.stream().map(LazyItemGetter::get).collect(Collectors.toList()); + } + public boolean isCraftable() { return !craftingIngredients.isEmpty(); } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java index bb0570d5..6be288e4 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java @@ -74,6 +74,18 @@ public void process(Player player) { return; } + if(!player.isGodMode() && item.hasSpawnSpacing() && zone.isSpawnInRange(x, y, item.getSpawnSpacing())) { + fail(player, String.format("%s must be at least %s blocks away from spawns.", item.getTitle(), item.getSpawnSpacing())); + return; + } + + if(!player.isGodMode() && item.hasSpacing() && zone.getMetaBlocks().stream().anyMatch(block + -> (item.hasSpacingItems() ? item.getSpacingItems().contains(block.getItem()) : block.getItem() == item) + && MathUtils.inRange(block.getX(), block.getY(), x, y, item.getSpacing()))) { + fail(player, String.format("%s must be at least %s blocks away from other %ss.", item.getTitle(), item.getSpacing(), item.getTitle().toLowerCase())); + return; + } + if(!player.isGodMode() && !item.canPlaceInField() && zone.isBlockProtected(x, y, player)) { fail(player, "This block is protected."); return; diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 4efe1b77..02c73786 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -1207,6 +1207,11 @@ public MetaBlock getRandomSpawnBlock() { return spawnBlocks.isEmpty() ? null : spawnBlocks.get((int)(Math.random() * spawnBlocks.size())); } + public boolean isSpawnInRange(int x, int y, double range) { + return metaBlocks.values().stream().anyMatch(block -> (block.getItem().hasId("mechanical/zone-teleporter") + || block.getItem().hasId("signs/obelisk-spawn")) && MathUtils.inRange(block.getX(), block.getY(), x, y, range)); + } + public List getMetaBlocksWithUse(ItemUseType useType) { return getMetaBlocks(metaBlock -> metaBlock.getItem().hasUse(useType)); } From 070664a0d15bb4f3f284e008daf52e4b01d65d85 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 22 Dec 2024 04:44:15 +0100 Subject: [PATCH 121/176] Add extra dialog input checks --- .../item/interactions/DialogInteraction.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java index 57201081..dadb60e7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java @@ -1,5 +1,6 @@ package brainwine.gameserver.item.interactions; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -66,13 +67,26 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i String key = MapHelper.getString(section, "input.key"); if(key != null) { + // TODO possibly verify input based on type String text = String.valueOf(data[i]); + int max = MapHelper.getInt(section, "input.max", MapHelper.getInt(section, "input.maxlength")); // Defaults to 0 = no limit + List options = MapHelper.getList(section, "input.options", Collections.EMPTY_LIST); // Get rid of text if player is currently muted if(player.isMuted() && MapHelper.getBoolean(section, "input.sanitize")) { text = text.replaceAll(".", "*"); } + // Shorten text if it is too long + if(max > 0 && text.length() > max) { + text = text.substring(0, max); + } + + // Check if input matches available options + if(!options.isEmpty() && !options.contains(text)) { + text = options.get(0); + } + metadata.put(key, text); } else if(MapHelper.getBoolean(section, "input.mod")) { List options = MapHelper.getList(section, "input.options"); From d7a89248bfb1bb45baf5f14ff0d997e2b5ac2ccd Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 22 Dec 2024 07:17:08 +0100 Subject: [PATCH 122/176] Catch & log dialog input handler exceptions --- .../java/brainwine/gameserver/player/Player.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index 430f0464..cf630ccc 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -14,6 +14,9 @@ import java.util.function.Consumer; import java.util.stream.Collectors; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import com.fasterxml.jackson.annotation.JsonCreator; import brainwine.gameserver.GameConfiguration; @@ -87,6 +90,7 @@ public class Player extends Entity implements CommandExecutor { public static final int REGEN_NO_DAMAGE_TIME = 10000; public static final float ENTITY_VISIBILITY_RANGE = 40; public static final float BASE_REGEN_AMOUNT = 0.1F; + private static final Logger logger = LogManager.getLogger(); private static int dialogDiscriminator; private final String documentId; private String email; @@ -609,8 +613,12 @@ public void handleDialogInput(int id, Object[] input) { notify("Sorry, the request has expired."); } } else { - // TODO since we're dealing with user input, should we just try-catch this? - handler.accept(input); + try { + handler.accept(input); + } catch(Exception e) { + logger.error("An error occured while handling dialog input", e); + notify("Oops! There was a problem processing your input."); + } } } From 8fb9010bd9ad083bf938d3d573d141edff69a8c5 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Mon, 23 Dec 2024 03:22:34 +0100 Subject: [PATCH 123/176] Player following system base --- .../gameserver/dialog/DialogSection.java | 10 +++ .../brainwine/gameserver/player/Player.java | 66 +++++++++++++++++ .../gameserver/player/PlayerConfigFile.java | 14 ++++ .../server/messages/FollowMessage.java | 28 ++++++++ .../gameserver/server/models/FollowData.java | 42 +++++++++++ .../server/requests/DialogRequest.java | 70 +++++++++++++++++-- .../server/requests/FollowRequest.java | 31 ++++++++ 7 files changed, 255 insertions(+), 6 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/server/messages/FollowMessage.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/server/models/FollowData.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/server/requests/FollowRequest.java diff --git a/gameserver/src/main/java/brainwine/gameserver/dialog/DialogSection.java b/gameserver/src/main/java/brainwine/gameserver/dialog/DialogSection.java index 5f6f48f2..813795b3 100644 --- a/gameserver/src/main/java/brainwine/gameserver/dialog/DialogSection.java +++ b/gameserver/src/main/java/brainwine/gameserver/dialog/DialogSection.java @@ -20,6 +20,7 @@ public class DialogSection { private List items = new ArrayList<>(); private String title; private String text; + private String choice; private String textColor; private double textScale; private Vector2i location; @@ -53,6 +54,15 @@ public String getText() { return text; } + public DialogSection setChoice(String choice) { + this.choice = choice; + return this; + } + + public String getChoice() { + return choice; + } + /** * v2 clients only! * For v3 clients, use {@link #setText} with HTML color tags. diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index cf630ccc..cddc655e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -10,6 +10,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -54,6 +55,7 @@ import brainwine.gameserver.server.messages.EntityPositionMessage; import brainwine.gameserver.server.messages.EntityStatusMessage; import brainwine.gameserver.server.messages.EventMessage; +import brainwine.gameserver.server.messages.FollowMessage; import brainwine.gameserver.server.messages.HealthMessage; import brainwine.gameserver.server.messages.HeartbeatMessage; import brainwine.gameserver.server.messages.InventoryMessage; @@ -106,6 +108,8 @@ public class Player extends Entity implements CommandExecutor { private List nameChanges; private List mutes; private List bans; + private Set followees; + private Set followers; private Set lootCodes; private Set achievements; private Map ignoredHints; @@ -159,6 +163,8 @@ protected Player(String documentId, PlayerConfigFile config) { this.nameChanges = config.getNameChanges(); this.mutes = config.getMutes(); this.bans = config.getBans(); + this.followees = config.getFollowees(); + this.followers = config.getFollowers(); this.lootCodes = config.getLootCodes(); this.achievements = config.getAchievements(); this.ignoredHints = config.getIgnoredHints(); @@ -180,6 +186,8 @@ public Player(String documentId, String name, Zone zone) { this.nameChanges = new ArrayList<>(); this.mutes = new ArrayList<>(); this.bans = new ArrayList<>(); + this.followees = new HashSet<>(); + this.followers = new HashSet<>(); this.lootCodes = new HashSet<>(); this.achievements = new HashSet<>(); this.ignoredHints = new HashMap<>(); @@ -949,6 +957,64 @@ protected List getAuthTokens() { return authTokens; } + public void followPlayer(Player player) { + if(!followees.add(player.getDocumentId())) { + return; // Do nothing if player is already following + } + + player.addFollower(this); + sendMessage(new FollowMessage(player, 0, true)); + } + + public void unfollowPlayer(Player player) { + if(!followees.remove(player.getDocumentId())) { + return; // Do nothing if player is not following + } + + player.removeFollower(this); + sendMessage(new FollowMessage(player, 0, false)); + } + + public boolean isFollowing(Player player) { + return isFollowing(player.getDocumentId()); + } + + public boolean isFollowing(String followee) { + return followees.contains(followee); + } + + public Set getFollowees() { + return Collections.unmodifiableSet(followees); + } + + private void addFollower(Player player) { + followers.add(player.getDocumentId()); + + if(isOnline()) { + sendMessage(new FollowMessage(player, 1, true)); + } + } + + private void removeFollower(Player player) { + followers.remove(player.getDocumentId()); + + if(isOnline()) { + sendMessage(new FollowMessage(player, 1, false)); + } + } + + public boolean hasFollower(Player player) { + return hasFollower(player.getDocumentId()); + } + + public boolean hasFollower(String follower) { + return followers.contains(follower); + } + + public Set getFollowers() { + return Collections.unmodifiableSet(followers); + } + public void addLootCode(String lootCode) { lootCodes.add(lootCode); } diff --git a/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java index e91d43c1..4437fddb 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java @@ -34,6 +34,8 @@ public class PlayerConfigFile { private List nameChanges = new ArrayList<>(); private List mutes = new ArrayList<>(); private List bans = new ArrayList<>(); + private Set followees = new HashSet<>(); + private Set followers = new HashSet<>(); private Set lootCodes = new HashSet<>(); private Set achievements = new HashSet<>(); private Map ignoredHints = new HashMap<>(); @@ -57,6 +59,8 @@ public PlayerConfigFile(Player player) { this.nameChanges = player.getNameChanges(); this.mutes = player.getMutes(); this.bans = player.getBans(); + this.followees = player.getFollowees(); + this.followers = player.getFollowers(); this.lootCodes = player.getLootCodes(); this.achievements = player.getAchievements(); this.ignoredHints = player.getIgnoredHints(); @@ -135,6 +139,16 @@ public PlayerStatistics getStatistics() { return statistics; } + @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) + public Set getFollowees() { + return followees; + } + + @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) + public Set getFollowers() { + return followers; + } + @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) public Set getLootCodes() { return lootCodes; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/messages/FollowMessage.java b/gameserver/src/main/java/brainwine/gameserver/server/messages/FollowMessage.java new file mode 100644 index 00000000..b9688d21 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/server/messages/FollowMessage.java @@ -0,0 +1,28 @@ +package brainwine.gameserver.server.messages; + +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; + +import brainwine.gameserver.player.Player; +import brainwine.gameserver.server.Message; +import brainwine.gameserver.server.MessageInfo; +import brainwine.gameserver.server.models.FollowData; + +@MessageInfo(id = 27, prepacked = true) +public class FollowMessage extends Message { + + public Collection followData; + + public FollowMessage(Collection followData) { + this.followData = followData; + } + + public FollowMessage(Player player, int direction, boolean following) { + this(Arrays.asList(new FollowData(player, direction, following))); + } + + public FollowMessage(Collection players, int direction) { + this(players.stream().map(player -> new FollowData(player, direction, true)).collect(Collectors.toList())); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/server/models/FollowData.java b/gameserver/src/main/java/brainwine/gameserver/server/models/FollowData.java new file mode 100644 index 00000000..752f44ce --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/server/models/FollowData.java @@ -0,0 +1,42 @@ +package brainwine.gameserver.server.models; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; + +import brainwine.gameserver.player.Player; + +@JsonFormat(shape = Shape.ARRAY) +public class FollowData { + + private final String playerName; + private final String playerId; + private final int direction; + private final boolean following; + + public FollowData(Player player, int direction, boolean following) { + this.playerName = player.getName(); + this.playerId = player.getDocumentId(); + this.direction = direction; + this.following = following; + } + + public FollowData(Player player, int direction) { + this(player, direction, true); + } + + public String getPlayerName() { + return playerName; + } + + public String getPlayerId() { + return playerId; + } + + public int getDirection() { + return direction; + } + + public boolean isFollowing() { + return following; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java index 66ed2358..1ec97bca 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java @@ -7,6 +7,7 @@ import org.apache.commons.text.WordUtils; +import brainwine.gameserver.GameServer; import brainwine.gameserver.dialog.Dialog; import brainwine.gameserver.dialog.DialogHelper; import brainwine.gameserver.dialog.DialogSection; @@ -16,6 +17,7 @@ import brainwine.gameserver.server.OptionalField; import brainwine.gameserver.server.PlayerRequest; import brainwine.gameserver.server.RequestInfo; +import brainwine.gameserver.zone.Zone; @RequestInfo(id = 45) public class DialogRequest extends PlayerRequest { @@ -33,12 +35,15 @@ public void process(Player player) { if(id instanceof String) { switch((String)id) { - case "skill_upgrade": - onSkillUpgrade(player); - break; - default: - player.notify("Sorry, this action is not implemented yet."); - break; + case "skill_upgrade": + onSkillUpgrade(player); + break; + case "player": + showPlayerDialog(player); + break; + default: + player.notify("Sorry, this action is not implemented yet."); + break; } return; } else if(id instanceof Integer) { @@ -48,6 +53,59 @@ public void process(Player player) { } } + private void showPlayerDialog(Player player) { + // Do nothing if there is no valid input data + if(input == null || input.length == 0 || !(input[0] instanceof String)) { + return; + } + + // Create player info dialog + Player subject = GameServer.getInstance().getPlayerManager().getPlayer((String)input[0]); + Dialog dialog = new Dialog().setTitle(subject.getName()); + + // Online status section + if(subject.isOnline()) { + dialog.addSection(new DialogSection().setText(String.format("Online\nCurrently in %s", subject.getZone().getName()))); + + if(player.getZone() != subject.getZone()) { + dialog.addSection(new DialogSection().setText(String.format("Goto %s", subject.getZone().getName())).setChoice("visit")); + } + } else { + dialog.addSection(new DialogSection().setText(String.format("Offline", subject.getZone().getName()))); + } + + // Follow section + String followText = player.isFollowing(subject) ? "Unfollow" : "Follow"; + dialog.addSection(new DialogSection().setText(followText).setChoice(followText.toLowerCase())); + + // Show player info dialog + player.showDialog(dialog, input -> { + // Handle cancellation + if(input.length == 1 && input[0].equals("cancel")) { + return; + } + + String choice = (String)input[0]; + + // Handle selection + switch(choice) { + case "follow": player.followPlayer(subject); break; + case "unfollow": player.unfollowPlayer(subject); break; + case "visit": + Zone zone = subject.getZone(); + + // TODO maybe perform checks in changeZone function and add a force flag for bypassing? + if(!zone.canJoin(player)) { + player.notify("Sorry, you can't enter this world right now."); + break; + } + + player.changeZone(subject.getZone()); + break; + } + }); + } + private void onSkillUpgrade(Player player) { if(player.getSkillPoints() <= 0) { player.notify("Sorry, you are out of skill points. Level up to earn some more!"); diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/FollowRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/FollowRequest.java new file mode 100644 index 00000000..23b31509 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/FollowRequest.java @@ -0,0 +1,31 @@ +package brainwine.gameserver.server.requests; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; + +@RequestInfo(id = 27) +public class FollowRequest extends PlayerRequest { + + public String recipientName; + public boolean following; + + @Override + public void process(Player player) { + Player recipient = GameServer.getInstance().getPlayerManager().getPlayer(recipientName); + + // Check if recipient exists + if(recipient == null) { + player.notify(String.format("Couldn't find a player named %s", recipientName)); + return; + } + + // Follow or unfollow player + if(following) { + player.followPlayer(recipient); + } else { + player.unfollowPlayer(recipient); + } + } +} From c682dff96038b57261e6a187e24ee422837a4ded Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Mon, 23 Dec 2024 03:46:37 +0100 Subject: [PATCH 124/176] Make players in god mode immune to drowning/thirst/cold --- .../brainwine/gameserver/player/Player.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index cddc655e..2af9476d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -10,7 +10,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -299,8 +298,7 @@ public boolean isSubmerged() { } public void applyBreath(float deltaTime) { - Item breathItem = getInventory().findAccessoryWithUse(ItemUseType.BREATH); - if(!breathItem.isAir()) { + if(isGodMode() || !inventory.findAccessoryWithUse(ItemUseType.BREATH).isAir()) { breath = 1.0; } else { if(isSubmerged()) { @@ -321,10 +319,17 @@ public void applyBreath(float deltaTime) { public void applyThirst(float deltaTime) { long now = System.currentTimeMillis(); - double thirstPeriod = MathUtils.lerp(5.0, 10.0, (getTotalSkillLevel(Skill.SURVIVAL) - 1) / 6.0) * 60; - int direction = zone.getBiome() == Biome.DESERT && !zone.isPurified() ? 1 : -1; - thirst = MathUtils.clamp(thirst + (direction * deltaTime / thirstPeriod), 0.0, 1.0); + // Update thirst stat + if(isGodMode()) { + thirst = 0.0; + } else { + double thirstPeriod = MathUtils.lerp(5.0, 10.0, (getTotalSkillLevel(Skill.SURVIVAL) - 1) / 6.0) * 60; + int direction = zone.getBiome() == Biome.DESERT && !zone.isPurified() ? 1 : -1; + thirst = MathUtils.clamp(thirst + (direction * deltaTime / thirstPeriod), 0.0, 1.0); + } + + // Send message if it is time if(now > lastThirstMessage + 1000) { sendMessage(new StatMessage(PlayerStat.THIRST, (float)thirst)); lastThirstMessage = now; @@ -352,9 +357,15 @@ public void applyThirst(float deltaTime) { public void applyFreeze(float deltaTime) { long now = System.currentTimeMillis(); - double freezePeriod = MathUtils.lerp(3.0, 10.0, (getTotalSkillLevel(Skill.SURVIVAL) - 1) / 6.0) * 60; - int direction = zone.getBiome() == Biome.ARCTIC ? 1 : -2; // Warm back up twice as fast - cold = MathUtils.clamp(cold + (direction * deltaTime / freezePeriod), 0.0, 1.0); + + // Update freeze stat + if(isGodMode()) { + cold = 0.0; + } else { + double freezePeriod = MathUtils.lerp(3.0, 10.0, (getTotalSkillLevel(Skill.SURVIVAL) - 1) / 6.0) * 60; + int direction = zone.getBiome() == Biome.ARCTIC ? 1 : -2; // Warm back up twice as fast + cold = MathUtils.clamp(cold + (direction * deltaTime / freezePeriod), 0.0, 1.0); + } // Send message & perform damage tick if it is time if(now > lastFreezeMessage + 1000) { From 93d1ca5e51af7398b7d730bf2c2f30041f42ee6b Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Mon, 23 Dec 2024 04:00:02 +0100 Subject: [PATCH 125/176] Send social info on join --- .../src/main/java/brainwine/gameserver/player/Player.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index 2af9476d..9c8760d2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -10,6 +10,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -508,6 +509,13 @@ public void onZoneChanged() { notify("Welcome to " + zone.getName(), NotificationType.WELCOME); } + // Send social info + PlayerManager playerManager = GameServer.getInstance().getPlayerManager(); + sendMessage(new FollowMessage(followees.stream().map(playerManager::getPlayerById).filter(Objects::nonNull).collect(Collectors.toList()), 0)); + sendMessage(new FollowMessage(followers.stream().map(playerManager::getPlayerById).filter(Objects::nonNull).collect(Collectors.toList()), 1)); + sendMessage(new EventMessage("socialInfoReady", null)); + + // Misc stuff updateAchievementProgress(JourneymanAchievement.class); checkRegistration(); } From ca74a78a6a05a1d6930bba9e8c6bcd4c116e6b45 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Mon, 23 Dec 2024 05:56:42 +0100 Subject: [PATCH 126/176] Hoo boy... --- .../item/interactions/DialogInteraction.java | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java index dadb60e7..d96cb23d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java @@ -65,29 +65,40 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i for(int i = 0; i < sections.size(); i++) { Map section = sections.get(i); String key = MapHelper.getString(section, "input.key"); + String type = MapHelper.getString(section, "input.type"); - if(key != null) { - // TODO possibly verify input based on type - String text = String.valueOf(data[i]); - int max = MapHelper.getInt(section, "input.max", MapHelper.getInt(section, "input.maxlength")); // Defaults to 0 = no limit + if(key != null && type != null) { List options = MapHelper.getList(section, "input.options", Collections.EMPTY_LIST); + String text = String.valueOf(data[i]); - // Get rid of text if player is currently muted - if(player.isMuted() && MapHelper.getBoolean(section, "input.sanitize")) { - text = text.replaceAll(".", "*"); - } - - // Shorten text if it is too long - if(max > 0 && text.length() > max) { - text = text.substring(0, max); - } - - // Check if input matches available options - if(!options.isEmpty() && !options.contains(text)) { - text = options.get(0); + switch(type) { + case "text": + int max = MapHelper.getInt(section, "input.max", MapHelper.getInt(section, "input.maxlength")); // Defaults to 0 = no limit + + // Get rid of text if player is currently muted + if(player.isMuted() && MapHelper.getBoolean(section, "input.sanitize")) { + text = text.replaceAll(".", "*"); + } + + // Shorten text if it is too long + if(max > 0 && text.length() > max) { + text = text.substring(0, max); + } + + metadata.put(key, text); + break; + case "text select": + // Check if input matches available options + if(!options.isEmpty() && !options.contains(text)) { + text = options.get(0); + } + + metadata.put(key, text); + break; + case "text index": + metadata.put(key, Math.max(0, Math.min(options.size() - 1, data[i] instanceof Integer ? (int)data[i] : 0))); + break; } - - metadata.put(key, text); } else if(MapHelper.getBoolean(section, "input.mod")) { List options = MapHelper.getList(section, "input.options"); From 6333485ff17125b3fc6dd042bbed77dafa91f021 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Mon, 23 Dec 2024 06:17:29 +0100 Subject: [PATCH 127/176] Allow followees through protectors that are configured to allow them --- .../java/brainwine/gameserver/zone/MetaBlock.java | 2 +- .../main/java/brainwine/gameserver/zone/Zone.java | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java b/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java index 6a27331c..f1784821 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/MetaBlock.java @@ -73,7 +73,7 @@ public boolean hasOwner() { } public boolean isOwnedBy(Player player) { - return player != null && player.getDocumentId().equals(owner); + return hasOwner() && player != null && player.getDocumentId().equals(owner); } @JsonIgnore diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 02c73786..56be8bc2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -617,6 +617,15 @@ public boolean isBlockProtected(int x, int y, Player player, Collection Date: Mon, 23 Dec 2024 06:56:17 +0100 Subject: [PATCH 128/176] Add odd cases --- .../gameserver/item/interactions/DialogInteraction.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java index d96cb23d..9741c06f 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/DialogInteraction.java @@ -88,6 +88,8 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i metadata.put(key, text); break; case "text select": + case "select": + case "color": // Check if input matches available options if(!options.isEmpty() && !options.contains(text)) { text = options.get(0); From 16b0c491681e3580512b45799b67e2e280825b3a Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Mon, 23 Dec 2024 18:28:18 +0100 Subject: [PATCH 129/176] Add target teleporter protected area bypass --- deepworld-config | 2 +- .../TargetTeleportInteraction.java | 23 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/deepworld-config b/deepworld-config index 665b1187..29ad0a9b 160000 --- a/deepworld-config +++ b/deepworld-config @@ -1 +1 @@ -Subproject commit 665b1187b5e645594ea80a986a7181904b938291 +Subproject commit 29ad0a9b55097fc86be128ea49bdec8d26a28184 diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/TargetTeleportInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/TargetTeleportInteraction.java index b04bac5e..a8fb199a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/TargetTeleportInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/TargetTeleportInteraction.java @@ -38,12 +38,17 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i player.notify(String.format("Cannot locate world '%s', please recalibrate.", zoneName)); return; } + + if(!targetZone.canJoin(player)) { + player.notify("Sorry, you can't enter this world right now."); + return; + } } // Parse target position int targetX = -1; int targetY = metaBlock.getIntProperty("py") + (targetZone.getBiome() == Biome.DEEP ? -1000 : 200); - int centerX = zone.getWidth() / 2; + int centerX = targetZone.getWidth() / 2; try { String strX = metaBlock.getStringProperty("px"); @@ -71,10 +76,17 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i return; } - // Do nothing if target location is protected + // Check area protection if(!player.isGodMode() && targetZone.isBlockProtected(targetX, targetY, player)) { - player.notify("That area is protected."); - return; + Player owner = metaBlock.getOwner(); + int setting = metaBlock.getIntProperty("pt"); + boolean ownerCanEdit = !targetZone.isBlockProtected(targetX, targetY, owner); + + // Check protection entry setting + if(owner == null || !ownerCanEdit || setting == 0 || (setting == 1 && !owner.isFollowing(player))) { + player.notify("That area is protected."); + return; + } } // Teleport the player to the target location @@ -93,7 +105,8 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i int _targetX = targetX; int _targetY = targetY; player.showDialog(dialog, input -> { - if(input.length == 1 && input[0].equals("Yes")) { + // TODO figure out this v2 quirk + if((!player.isV3() && input.length == 0) || (input.length == 1 && input[0].equals("Yes"))) { player.changeZone(_targetZone, _targetX, _targetY); } }); From 8dad9c45e0637bfa24846fd9649301a25a18799a Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Mon, 23 Dec 2024 21:15:20 +0100 Subject: [PATCH 130/176] Fix player dialog exception --- .../brainwine/gameserver/server/requests/DialogRequest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java index 1ec97bca..924fa3f7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java @@ -81,11 +81,11 @@ private void showPlayerDialog(Player player) { // Show player info dialog player.showDialog(dialog, input -> { // Handle cancellation - if(input.length == 1 && input[0].equals("cancel")) { + if(input.length == 0 || (input.length == 1 && input[0].equals("cancel"))) { return; } - String choice = (String)input[0]; + String choice = String.valueOf(input[0]); // Handle selection switch(choice) { From 27b796d3a90235b7157bca9ad5281d2aadbccc55 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Wed, 25 Dec 2024 01:43:33 +0100 Subject: [PATCH 131/176] Track recent & bookmarked zones --- .../main/java/brainwine/api/DataFetcher.java | 2 + .../brainwine/api/DefaultDataFetcher.java | 10 +++ .../java/brainwine/api/PortalService.java | 11 +-- .../brainwine/gameserver/player/Player.java | 52 +++++++++++++ .../gameserver/player/PlayerConfigFile.java | 14 ++++ .../server/requests/BookmarkRequest.java | 40 ++++++++++ .../server/requests/ZoneSearchRequest.java | 75 ++++++++++++------- .../java/brainwine/gameserver/zone/Zone.java | 1 + .../java/brainwine/DirectDataFetcher.java | 24 ++++++ 9 files changed, 196 insertions(+), 33 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/server/requests/BookmarkRequest.java diff --git a/api/src/main/java/brainwine/api/DataFetcher.java b/api/src/main/java/brainwine/api/DataFetcher.java index 36385fde..b32ea475 100644 --- a/api/src/main/java/brainwine/api/DataFetcher.java +++ b/api/src/main/java/brainwine/api/DataFetcher.java @@ -14,4 +14,6 @@ public interface DataFetcher { public boolean verifyApiToken(String apiToken); public ZoneInfo getZoneInfo(String nameOrId); public Collection fetchZoneInfo(); + public Collection fetchRecentZoneInfo(String apiToken); + public Collection fetchBookmarkedZoneInfo(String apiToken); } diff --git a/api/src/main/java/brainwine/api/DefaultDataFetcher.java b/api/src/main/java/brainwine/api/DefaultDataFetcher.java index cc738985..a9d65e44 100644 --- a/api/src/main/java/brainwine/api/DefaultDataFetcher.java +++ b/api/src/main/java/brainwine/api/DefaultDataFetcher.java @@ -47,4 +47,14 @@ public ZoneInfo getZoneInfo(String nameOrId) { public Collection fetchZoneInfo() { throw exception; } + + @Override + public Collection fetchRecentZoneInfo(String apiToken) { + throw exception; + } + + @Override + public Collection fetchBookmarkedZoneInfo(String apiToken) { + throw exception; + } } diff --git a/api/src/main/java/brainwine/api/PortalService.java b/api/src/main/java/brainwine/api/PortalService.java index 68049d8a..b2e5cb03 100644 --- a/api/src/main/java/brainwine/api/PortalService.java +++ b/api/src/main/java/brainwine/api/PortalService.java @@ -6,6 +6,7 @@ import java.awt.image.BufferedImage; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -114,7 +115,11 @@ private void handleZoneSearch(Context ctx) { return; } - final List zones = (List)dataFetcher.fetchZoneInfo(); // TODO this will probably be slow if there is a large number of zones + // TODO filtering is a bit convoluted, see if we can make it more efficient in the future. + String account = ctx.queryParam("account"); + final List zones = account == null ? (List)dataFetcher.fetchZoneInfo() + : account.equals("recent") ? (List)dataFetcher.fetchRecentZoneInfo(apiToken) + : account.equals("bookmarked") ? (List)dataFetcher.fetchBookmarkedZoneInfo(apiToken) : new ArrayList<>(); zones.removeIf(zone -> zone.isPrivate() && !apiToken.equals(zone.getOwner()) && !zone.getMembers().contains(apiToken)); handleQueryParam(ctx, "name", String.class, name -> { @@ -151,10 +156,6 @@ private void handleZoneSearch(Context ctx) { } }); - handleQueryParam(ctx, "account", String.class, account -> { - zones.clear(); // not supported yet - }); - handleQueryParam(ctx, "sort", String.class, sort -> { switch(sort) { case "popularity": // Sort by most players first diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index 9c8760d2..c51659a5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -82,6 +82,8 @@ public class Player extends Entity implements CommandExecutor { + public static final int RECENT_ZONE_LIMIT = 12; + public static final int BOOKMARKED_ZONE_LIMIT = 50; public static final int MAX_SKILL_LEVEL = 15; public static final int MAX_NATURAL_SKILL_LEVEL = 10; public static final int MAX_SPEED_X = 12; @@ -108,6 +110,8 @@ public class Player extends Entity implements CommandExecutor { private List nameChanges; private List mutes; private List bans; + private List recentZones; + private List bookmarkedZones; private Set followees; private Set followers; private Set lootCodes; @@ -163,6 +167,8 @@ protected Player(String documentId, PlayerConfigFile config) { this.nameChanges = config.getNameChanges(); this.mutes = config.getMutes(); this.bans = config.getBans(); + this.recentZones = config.getRecentZones(); + this.bookmarkedZones = config.getBookmarkedZones(); this.followees = config.getFollowees(); this.followers = config.getFollowers(); this.lootCodes = config.getLootCodes(); @@ -186,6 +192,8 @@ public Player(String documentId, String name, Zone zone) { this.nameChanges = new ArrayList<>(); this.mutes = new ArrayList<>(); this.bans = new ArrayList<>(); + this.recentZones = new ArrayList<>(); + this.bookmarkedZones = new ArrayList<>(); this.followees = new HashSet<>(); this.followers = new HashSet<>(); this.lootCodes = new HashSet<>(); @@ -518,6 +526,12 @@ public void onZoneChanged() { // Misc stuff updateAchievementProgress(JourneymanAchievement.class); checkRegistration(); + recentZones.remove(zone.getDocumentId()); // Remove first in case the zone has already been visited recently + recentZones.add(0, zone.getDocumentId()); // Add at top so we don't have to reverse the list for the zone searcher + + while(recentZones.size() > RECENT_ZONE_LIMIT) { + recentZones.remove(recentZones.size() - 1); + } } /** @@ -976,6 +990,44 @@ protected List getAuthTokens() { return authTokens; } + public List getRecentZones() { + return Collections.unmodifiableList(recentZones); + } + + public void addZoneBookmark(Zone zone) { + addZoneBookmark(zone.getDocumentId()); + } + + public void addZoneBookmark(String zone) { + if(!isZoneBookmarked(zone)) { + bookmarkedZones.add(0, zone); // Add at top for zone searcher + } + } + + public void removeZoneBookmark(Zone zone) { + bookmarkedZones.remove(zone.getDocumentId()); + } + + public void removeZoneBookmark(String zone) { + bookmarkedZones.remove(zone); + } + + public boolean isZoneBookmarked(Zone zone) { + return isZoneBookmarked(zone.getDocumentId()); + } + + public boolean isZoneBookmarked(String zone) { + return bookmarkedZones.contains(zone); + } + + public int getBookmarkedZoneCount() { + return bookmarkedZones.size(); + } + + public List getBookmarkedZones() { + return Collections.unmodifiableList(bookmarkedZones); + } + public void followPlayer(Player player) { if(!followees.add(player.getDocumentId())) { return; // Do nothing if player is already following diff --git a/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java index 4437fddb..5c739b5e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java @@ -34,6 +34,8 @@ public class PlayerConfigFile { private List nameChanges = new ArrayList<>(); private List mutes = new ArrayList<>(); private List bans = new ArrayList<>(); + private List recentZones = new ArrayList<>(); + private List bookmarkedZones = new ArrayList<>(); private Set followees = new HashSet<>(); private Set followers = new HashSet<>(); private Set lootCodes = new HashSet<>(); @@ -59,6 +61,8 @@ public PlayerConfigFile(Player player) { this.nameChanges = player.getNameChanges(); this.mutes = player.getMutes(); this.bans = player.getBans(); + this.recentZones = player.getRecentZones(); + this.bookmarkedZones = player.getBookmarkedZones(); this.followees = player.getFollowees(); this.followers = player.getFollowers(); this.lootCodes = player.getLootCodes(); @@ -113,6 +117,16 @@ public List getBans() { return bans; } + @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) + public List getRecentZones() { + return recentZones; + } + + @JsonSetter(nulls = Nulls.SKIP, contentNulls = Nulls.SKIP) + public List getBookmarkedZones() { + return bookmarkedZones; + } + public int getExperience() { return experience; } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BookmarkRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BookmarkRequest.java new file mode 100644 index 00000000..5b74bdf9 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BookmarkRequest.java @@ -0,0 +1,40 @@ +package brainwine.gameserver.server.requests; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.server.PlayerRequest; +import brainwine.gameserver.server.RequestInfo; + +@RequestInfo(id = 62) +public class BookmarkRequest extends PlayerRequest { + + public String type; // Looks like multiple bookmark types were planned, but only "zone" ended up being added. + public String id; + public boolean active; + + @Override + public void process(Player player) { + // Check type + if(!type.equals("zone")) { + return; + } + + // Check if zone exists + if(GameServer.getInstance().getZoneManager().getZone(id) == null) { + return; + } + + // Add or remove bookmark + if(active) { + if(player.getBookmarkedZoneCount() > Player.BOOKMARKED_ZONE_LIMIT) { + player.notify("Error: Bookmark limit reached, please delete some bookmarks."); + return; + } + + player.addZoneBookmark(id); + } else { + player.removeZoneBookmark(id); + } + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneSearchRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneSearchRequest.java index c9863a41..7a3edbf7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneSearchRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/ZoneSearchRequest.java @@ -15,6 +15,7 @@ import brainwine.gameserver.server.models.ZoneSearchData; import brainwine.gameserver.zone.Biome; import brainwine.gameserver.zone.Zone; +import brainwine.gameserver.zone.ZoneManager; @RequestInfo(id = 23) public class ZoneSearchRequest extends PlayerRequest { @@ -23,15 +24,33 @@ public class ZoneSearchRequest extends PlayerRequest { @Override public void process(Player player) { - Collection allZones = GameServer.getInstance().getZoneManager().getZones(); int searchLimit = 200; int displayLimit = 10; + ZoneManager zoneManager = GameServer.getInstance().getZoneManager(); + Collection zoneIds = null; + Collection zones = null; - // Find zones matching the filter - List zones = allZones.stream() - .filter(getFilter(player).and(zone -> zone != player.getZone())) - .limit(searchLimit) - .collect(Collectors.toList()); + // Get list of zones to filter + // TODO friends + switch(type) { + case "Recent": zoneIds = player.getRecentZones(); break; + case "Bookmarked": zoneIds = player.getBookmarkedZones(); break; + default: break; + } + + if(zoneIds != null) { + // Get zones from pre-assembled list of zone IDs + zones = zoneIds.stream() + .map(zoneManager::getZone) + .filter(zone -> zone != null && zone != player.getZone() && zone.canJoin(player)) + .collect(Collectors.toList()); + } else { + // Find zones matching the filter + zones = zoneManager.getZones().stream() + .filter(getFilter(player).and(zone -> zone != player.getZone())) + .limit(searchLimit) + .collect(Collectors.toList()); + } // Get random zones to display List data = zones.stream() @@ -47,28 +66,28 @@ public void process(Player player) { private Predicate getFilter(Player player) { switch(type) { - case "Random": - return zone -> zone.isPublic(); - case "Popular": - return zone -> zone.isPublic() && zone.isPopular(); - case "Unexplored": - return zone -> zone.isPublic() && !zone.isProtected() && zone.isUnexplored(); - case "Owned": - return zone -> zone.isOwner(player); - case "Member": - return zone -> zone.isMember(player); - case "PvP": - return zone -> zone.isPublic() && zone.isPvp(); - case "Plain": - case "Hell": - case "Arctic": - case "Desert": - case "Brain": - case "Deep": - case "Space": - return zone -> zone.isPublic() && !zone.isProtected() && zone.getBiome() == Biome.valueOf(type.toUpperCase()); - default: - return zone -> zone.isPublic() && zone.getName().toLowerCase().contains(type.toLowerCase()); + case "Random": + return zone -> zone.isPublic(); + case "Popular": + return zone -> zone.isPublic() && zone.isPopular(); + case "Unexplored": + return zone -> zone.isPublic() && !zone.isProtected() && zone.isUnexplored(); + case "Owned": + return zone -> zone.isOwner(player); + case "Member": + return zone -> zone.isMember(player); + case "PvP": + return zone -> zone.isPublic() && zone.isPvp(); + case "Plain": + case "Hell": + case "Arctic": + case "Desert": + case "Brain": + case "Deep": + case "Space": + return zone -> zone.isPublic() && !zone.isProtected() && zone.getBiome() == Biome.valueOf(type.toUpperCase()); + default: + return zone -> zone.isPublic() && zone.getName().toLowerCase().contains(type.toLowerCase()); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index 56be8bc2..c61b291e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -1840,6 +1840,7 @@ public Map getClientConfig(Player player) { config.put("owner", isOwner(player)); config.put("member", isMember(player)); config.put("pvp", pvp); + config.put("bookmarked", player.isZoneBookmarked(this)); Map depth = new HashMap<>(); List earth = new ArrayList<>(); diff --git a/src/main/java/brainwine/DirectDataFetcher.java b/src/main/java/brainwine/DirectDataFetcher.java index b47b5e89..d25c92dc 100644 --- a/src/main/java/brainwine/DirectDataFetcher.java +++ b/src/main/java/brainwine/DirectDataFetcher.java @@ -3,6 +3,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import brainwine.api.DataFetcher; import brainwine.api.models.ZoneInfo; @@ -63,6 +65,9 @@ public ZoneInfo getZoneInfo(String nameOrId) { return zone == null ? null : createZoneInfo(zone); } + /** + * TODO this will probably be slow if there is a large number of zones + */ @Override public Collection fetchZoneInfo() { List zoneInfo = new ArrayList<>(); @@ -74,6 +79,25 @@ public Collection fetchZoneInfo() { return zoneInfo; } + + @Override + public Collection fetchRecentZoneInfo(String apiToken) { + Player player = playerManager.getPlayerById(apiToken); + return player == null ? new ArrayList<>() : createZoneInfo(player.getRecentZones()); + } + + @Override + public Collection fetchBookmarkedZoneInfo(String apiToken) { + Player player = playerManager.getPlayerById(apiToken); + return player == null ? new ArrayList<>() : createZoneInfo(player.getBookmarkedZones()); + } + + private List createZoneInfo(Collection zoneIds) { + return zoneIds.stream().map(zoneManager::getZone) + .filter(Objects::nonNull) + .map(DirectDataFetcher::createZoneInfo) + .collect(Collectors.toCollection(ArrayList::new)); + } private static ZoneInfo createZoneInfo(Zone zone) { return new ZoneInfo(zone.getName(), From c550e2026ebfea0b1e01032f464b3ae7f3459d15 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Wed, 25 Dec 2024 01:53:06 +0100 Subject: [PATCH 132/176] Persist zone PvP state --- .../src/main/java/brainwine/gameserver/zone/Zone.java | 1 + .../java/brainwine/gameserver/zone/ZoneConfigFile.java | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index c61b291e..e0a421a3 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -126,6 +126,7 @@ protected Zone(String documentId, ZoneConfigFile config, ZoneDataFile data) { acidity = biome == Biome.ARCTIC || biome == Biome.SPACE ? 0 : config.getAcidity(); isPrivate = config.isPrivate(); isProtected = config.isProtected(); + pvp = config.isPvp(); creationDate = config.getCreationDate(); } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java index cda07604..d1e29184 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java @@ -38,6 +38,9 @@ public class ZoneConfigFile { @JsonSetter(value = "protected") private boolean isProtected; + @JsonSetter(nulls = Nulls.SKIP) + private boolean pvp; + @JsonSetter(nulls = Nulls.SKIP) private String owner; @@ -73,6 +76,7 @@ public ZoneConfigFile(Zone zone) { this.acidity = zone.getAcidity(); this.isPrivate = zone.isPrivate(); this.isProtected = zone.isProtected(); + this.pvp = zone.isPvp(); this.owner = zone.getOwner(); this.members = zone.getMembers(); this.discoveredParts = zone.getDiscoveredParts(); @@ -108,6 +112,10 @@ public boolean isProtected() { return isProtected; } + public boolean isPvp() { + return pvp; + } + public String getOwner() { return owner; } From 2fb1908e9a210b75d58275eba689e048f9b05c8b Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Wed, 25 Dec 2024 19:02:59 +0100 Subject: [PATCH 133/176] Clear invalid bookmarks on zone enter --- .../src/main/java/brainwine/gameserver/player/Player.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index c51659a5..14cbf76a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -79,6 +79,7 @@ import brainwine.gameserver.zone.Chunk; import brainwine.gameserver.zone.MetaBlock; import brainwine.gameserver.zone.Zone; +import brainwine.gameserver.zone.ZoneManager; public class Player extends Entity implements CommandExecutor { @@ -523,6 +524,10 @@ public void onZoneChanged() { sendMessage(new FollowMessage(followers.stream().map(playerManager::getPlayerById).filter(Objects::nonNull).collect(Collectors.toList()), 1)); sendMessage(new EventMessage("socialInfoReady", null)); + // Clear invalid bookmarks + ZoneManager zoneManager = GameServer.getInstance().getZoneManager(); + bookmarkedZones.removeIf(bookmark -> zoneManager.getZone(bookmark) == null || !zoneManager.getZone(bookmark).canJoin(this)); + // Misc stuff updateAchievementProgress(JourneymanAchievement.class); checkRegistration(); From 3e0ec841d1c2d8ff50568226e8deea7fc822bc8a Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 26 Dec 2024 06:15:40 +0100 Subject: [PATCH 134/176] Surely this won't break anything... right? --- .../java/brainwine/gameserver/player/TradeSession.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/TradeSession.java b/gameserver/src/main/java/brainwine/gameserver/player/TradeSession.java index 575461d0..aa7e8c4e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/TradeSession.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/TradeSession.java @@ -218,7 +218,7 @@ private void onInitiatorGiveFreely() { } // End trade if recipient is currently unavailable - if(!canTrade(recipient)) { + if(!canRecipientTrade()) { initiator.showDialog(DialogHelper.messageDialog(String.format("%s cannot receive items right now -- try again in a minute.", recipient.getName()))); end(); return; @@ -262,7 +262,7 @@ private void onInitiatorSendTradeRequest() { } // End trade if recipient is unavailable - if(!canTrade(recipient)) { + if(!canRecipientTrade()) { initiator.showDialog(DialogHelper.messageDialog(String.format("%s cannot trade right now -- try again in a minute.", recipient.getName()))); end(); return; @@ -528,9 +528,9 @@ public boolean isParticipant(Player player) { } /** - * @return {@code true} if the given player is available to trade right now, otherwise {@code false}. + * @return {@code true} if the recipient available to trade right now, otherwise {@code false}. */ - public boolean canTrade(Player player) { + public boolean canRecipientTrade() { return recipient.isOnline() && recipient.getZone() == initiator.getZone() && !recipient.isTrading(); } From 3a0a2250e842203a01a65829327326ee5ab492fe Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 28 Dec 2024 16:50:51 +0100 Subject: [PATCH 135/176] Allow `/tp` command to change zones --- .../command/admin/TeleportCommand.java | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/command/admin/TeleportCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/TeleportCommand.java index c9214f24..ac1cce75 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/admin/TeleportCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/TeleportCommand.java @@ -2,10 +2,12 @@ import static brainwine.gameserver.player.NotificationType.SYSTEM; +import brainwine.gameserver.GameServer; import brainwine.gameserver.command.Command; import brainwine.gameserver.command.CommandExecutor; import brainwine.gameserver.command.CommandInfo; import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.PlayerManager; import brainwine.gameserver.zone.Zone; @CommandInfo(name = "teleport", description = "Teleports you or another player to the specified target position or player.", aliases = "tp") @@ -18,21 +20,27 @@ public void execute(CommandExecutor executor, String[] args) { return; } + PlayerManager playerManager = GameServer.getInstance().getPlayerManager(); Player player = (Player)executor; Player subject = player; // Player that is being teleported (executor by default) - Zone zone = player.getZone(); + Zone targetZone = subject.getZone(); int x = 0; int y = 0; if(args.length == 1) { // Teleport executor to target player - Player target = zone.getPlayer(args[0]); + Player target = playerManager.getPlayer(args[0]); if(target == null) { player.notify(String.format("Player '%s' not found.", args[0])); return; } + if(!target.isOnline()) { + player.notify(String.format("Player '%s' is not online.", target.getName())); + return; + } + if(subject == target) { player.notify("You cannot teleport to yourself."); return; @@ -40,6 +48,7 @@ public void execute(CommandExecutor executor, String[] args) { x = target.getBlockX(); y = target.getBlockY(); + targetZone = target.getZone(); } else if(args.length == 2) { // Teleport executor to target position OR teleport subject to player try { @@ -47,14 +56,19 @@ public void execute(CommandExecutor executor, String[] args) { y = Integer.parseInt(args[1]); } catch(NumberFormatException e) { // If first 2 params are not numbers then we are probably teleporting a player to another player - subject = zone.getPlayer(args[0]); // Do null check later - Player target = zone.getPlayer(args[1]); + subject = playerManager.getPlayer(args[0]); // Do null check later + Player target = playerManager.getPlayer(args[1]); if(target == null) { player.notify(String.format("Player '%s' not found.", args[1])); return; } + if(!target.isOnline()) { + player.notify(String.format("Player '%s' is not online.", target.getName())); + return; + } + if(subject == target) { player.notify("You cannot teleport a player to themselves."); return; @@ -62,10 +76,11 @@ public void execute(CommandExecutor executor, String[] args) { x = target.getBlockX(); y = target.getBlockY(); + targetZone = target.getZone(); } } else if(args.length == 3) { // Teleport subject to a position - subject = zone.getPlayer(args[0]); // Do null check later + subject = playerManager.getPlayer(args[0]); // Do null check later try { x = Integer.parseInt(args[1]); @@ -82,13 +97,22 @@ public void execute(CommandExecutor executor, String[] args) { return; } + if(!subject.isOnline()) { + player.notify(String.format("Player '%s' is not online.", subject.getName())); + return; + } + // Check if coordinates are in bounds if(!player.getZone().areCoordinatesInBounds(x, y)) { player.notify("Cannot teleport out of bounds!", SYSTEM); return; } - subject.teleport(x, y); + if(targetZone == subject.getZone()) { + subject.teleport(x, y); + } else { + subject.changeZone(targetZone, x, y); + } } @Override From 774d8c7b14bd280b2df6e2b3f6c6420d2c686df7 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 29 Dec 2024 05:02:19 +0100 Subject: [PATCH 136/176] Fix blunder --- .../brainwine/gameserver/command/admin/TeleportCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/command/admin/TeleportCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/TeleportCommand.java index ac1cce75..3f59df01 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/admin/TeleportCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/TeleportCommand.java @@ -103,7 +103,7 @@ public void execute(CommandExecutor executor, String[] args) { } // Check if coordinates are in bounds - if(!player.getZone().areCoordinatesInBounds(x, y)) { + if(!targetZone.areCoordinatesInBounds(x, y)) { player.notify("Cannot teleport out of bounds!", SYSTEM); return; } From 0dfb22f20dbb7926ca4d79b955befd8552cfee2e Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 29 Dec 2024 22:14:31 +0100 Subject: [PATCH 137/176] Check item tradeability --- .../java/brainwine/gameserver/item/Item.java | 7 ++++++ .../gameserver/item/Tradeability.java | 22 +++++++++++++++++++ .../brainwine/gameserver/player/Player.java | 13 +++++++++++ 3 files changed, 42 insertions(+) create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/Tradeability.java diff --git a/gameserver/src/main/java/brainwine/gameserver/item/Item.java b/gameserver/src/main/java/brainwine/gameserver/item/Item.java index 584a60b1..9d67eb35 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/Item.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/Item.java @@ -42,6 +42,9 @@ public class Item { @JsonProperty("fieldable") private Fieldability fieldability = Fieldability.TRUE; + @JsonProperty("tradeable") + private Tradeability tradeability = Tradeability.TRUE; + @JsonProperty("loot_graphic") private DialogType lootGraphic = DialogType.STANDARD; @@ -289,6 +292,10 @@ public Fieldability getFieldability() { return fieldability; } + public Tradeability getTradeability() { + return tradeability; + } + public DialogType getLootGraphic() { return lootGraphic; } diff --git a/gameserver/src/main/java/brainwine/gameserver/item/Tradeability.java b/gameserver/src/main/java/brainwine/gameserver/item/Tradeability.java new file mode 100644 index 00000000..6a5d627f --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/Tradeability.java @@ -0,0 +1,22 @@ +package brainwine.gameserver.item; + +import com.fasterxml.jackson.annotation.JsonCreator; + +public enum Tradeability { + + TRUE, + FALSE, + LEVELED; + + @JsonCreator + private static Tradeability create(String string) { + switch(string) { + default: + return TRUE; + case "false": + return FALSE; + case "leveled": + return LEVELED; + } + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index 14cbf76a..bbd87bbb 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -41,6 +41,7 @@ import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; import brainwine.gameserver.item.MiningBonus; +import brainwine.gameserver.item.Tradeability; import brainwine.gameserver.item.consumables.Consumable; import brainwine.gameserver.loot.Loot; import brainwine.gameserver.server.Message; @@ -822,6 +823,18 @@ public void tradeItem(Player recipient, Item item) { return; } + // Check if item is tradeable + if(!isGodMode() && item.getTradeability() == Tradeability.FALSE) { + notify("Sorry, you cannot trade this item."); + return; + } + + // Check if player is high enough level to trade this item + if(!isGodMode() && item.getTradeability() == Tradeability.LEVELED && getLevel() < 20) { + notify("You must be level 20+ to trade this item."); + return; + } + // Cancel the current trade if the player is initiating a new trade if(isTrading() && !tradeSession.isParticipant(recipient)) { tradeSession.cancel(this); From 214cbcef3937a8d9594ab9bc31cc00af2ea865f9 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:35:55 +0100 Subject: [PATCH 138/176] Add `/crowns`command --- .../command/admin/CrownsCommand.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 gameserver/src/main/java/brainwine/gameserver/command/admin/CrownsCommand.java diff --git a/gameserver/src/main/java/brainwine/gameserver/command/admin/CrownsCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/CrownsCommand.java new file mode 100644 index 00000000..45f2d873 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/CrownsCommand.java @@ -0,0 +1,89 @@ +package brainwine.gameserver.command.admin; + +import static brainwine.gameserver.player.NotificationType.SYSTEM; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; + +@CommandInfo(name = "crowns", description = "Display or update a player's crown balance.") +public class CrownsCommand extends Command { + + @Override + public void execute(CommandExecutor executor, String[] args) { + if(args.length == 0 || args.length == 2) { + executor.notify(String.format("Usage: %s", getUsage(executor)), SYSTEM); + return; + } + + Player target = GameServer.getInstance().getPlayerManager().getPlayer(args[0]); + + // Check if player exists + if(target == null) { + executor.notify("This player does not exist.", SYSTEM); + return; + } + + // Show number of crowns if no further arguments are given + if(args.length == 1) { + executor.notify(String.format("%s has %s crowns.", target.getName(), target.getCrowns()), SYSTEM); + return; + } + + int amount = 0; + int currentAmount = target.getCrowns(); + + try { + amount = Integer.parseInt(args[2]); + } catch(NumberFormatException e) { + executor.notify("Amount must be a valid number.", SYSTEM); + return; + } + + // Update target player's crown balance + switch(args[1]) { + case "set": + target.setCrowns(Math.max(0, amount)); + break; + case "add": + target.setCrowns(Math.max(0, currentAmount + amount)); // Can overflow but realistically won't matter + break; + case "remove": + target.setCrowns(Math.max(0, currentAmount - amount)); + break; + default: + executor.notify(String.format("Usage: %s", getUsage(executor)), SYSTEM); + return; + } + + // Calculate balance difference and send notifications + int difference = target.getCrowns() - currentAmount; + amount = Math.abs(difference); + + if(difference > 0) { + executor.notify(String.format("Gave %s crown%s to %s. (New balance: %s, was: %s)", amount, amount == 1 ? "" : "s", target.getName(), target.getCrowns(), currentAmount), SYSTEM); + target.notify(String.format("You've received %s crown%s from an administrator.", amount, amount == 1 ? "" : "s"), SYSTEM); + return; + } + + if(difference < 0) { + executor.notify(String.format("Took %s crown%s from %s. (New balance: %s, was: %s)", amount, amount == 1 ? "" : "s", target.getName(), target.getCrowns(), currentAmount), SYSTEM); + target.notify(String.format("%s crown%s %s been taken from your account by an administrator.", amount, amount == 1 ? "" : "s", amount == 1 ? "has" : "have"), SYSTEM); + return; + } + + executor.notify(String.format("No changes were made to %s's crown balance.", target.getName()), SYSTEM); + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/crowns [ ]"; + } + + @Override + public boolean canExecute(CommandExecutor executor) { + return executor.isAdmin(); + } +} From d4874b25db18eb850a4449d7006a0066ccb671db Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Mon, 30 Dec 2024 21:15:04 +0100 Subject: [PATCH 139/176] Fix inventory use desync for real this time, I hope... --- .../server/requests/InventoryUseRequest.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java index ee5b3582..5b800eac 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/InventoryUseRequest.java @@ -45,35 +45,35 @@ public void process(Player player) { player.setHeldItem(item); } - // Send item use data to other players in the zone - player.sendMessageToTrackers(new EntityItemUseMessage(player.getId(), type, item, status)); - // Lovely type ambiguity. Always nice. if(item.isWeapon() && status == 1) { Collection entityIds = details instanceof Collection ? (Collection)details : details instanceof Integer ? Arrays.asList((int)details) : null; - // Skip if null aka details was of an invalid type - if(entityIds == null) { - return; - } - - int maxTargetableEntities = player.getMaxTargetableEntities(); - - for(Object id : entityIds) { - if(id instanceof Integer) { - Npc npc = player.getZone().getNpc((int)id); + // Attack enemies if details are present + if(entityIds != null && !entityIds.isEmpty()) { + int maxTargetableEntities = player.getMaxTargetableEntities(); + + for(Object id : entityIds) { + if(id instanceof Integer) { + Npc npc = player.getZone().getNpc((int)id); + + if(npc != null && (player.isGodMode() || (player.canSee(npc) && !npc.wasAttackedRecently(player, Entity.ATTACK_INVINCIBLE_TIME)))) { + npc.attack(player, item, item.getDamage(), item.getDamageType()); + } + } - if(npc != null && (player.isGodMode() || (player.canSee(npc) && !npc.wasAttackedRecently(player, Entity.ATTACK_INVINCIBLE_TIME)))) { - npc.attack(player, item, item.getDamage(), item.getDamageType()); + if(--maxTargetableEntities <= 0) { + break; } } - if(--maxTargetableEntities <= 0) { - break; - } + return; } } + + // Send item use data to other players in the zone if no details are present + player.sendMessageToTrackers(new EntityItemUseMessage(player.getId(), type, item, status)); } } } From 260d234f13566cc7af3c49e29fa3da9cb7291aa6 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Wed, 1 Jan 2025 19:30:34 +0100 Subject: [PATCH 140/176] Refactor `drawSurfaceMap` function --- .../main/java/brainwine/api/MapRenderer.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/api/src/main/java/brainwine/api/MapRenderer.java b/api/src/main/java/brainwine/api/MapRenderer.java index ba4916c6..1115e21e 100644 --- a/api/src/main/java/brainwine/api/MapRenderer.java +++ b/api/src/main/java/brainwine/api/MapRenderer.java @@ -49,16 +49,19 @@ private static BufferedImage loadImageResource(String name) { return null; } + public static BufferedImage drawSurfaceMap(ZoneInfo zone) { + return drawSurfaceMap(zone.getWidth(), zone.getHeight(), zone.getBiome(), zone.getSurface()); + } + /** * Creates a surface map render that is visually almost identical to V2 client map renders. */ - public static BufferedImage drawSurfaceMap(ZoneInfo zone) { - BufferedImage image = ImageUtils.createImage(300000, zone.getWidth(), zone.getHeight()); + public static BufferedImage drawSurfaceMap(int width, int height, String biome, int[] surfaceArray) { + BufferedImage image = ImageUtils.createImage(300000, width, height); Graphics2D g2d = image.createGraphics(); - int[] surfaceArray = zone.getSurface(); - int[] colors = colorMap.getOrDefault(zone.getBiome(), colorMap.get("plain")); + int[] colors = colorMap.getOrDefault(biome.toLowerCase(), colorMap.get("plain")); int[] raster = ((DataBufferInt)image.getRaster().getDataBuffer()).getData(); - int surfaceMin = zone.getHeight(); + int surfaceMin = height; int surfaceMax = 0; // Find highest & lowest surface points @@ -68,8 +71,8 @@ public static BufferedImage drawSurfaceMap(ZoneInfo zone) { } int surfaceCenter = (int)Math.floor((surfaceMax - surfaceMin) * 0.5 + surfaceMin); - double scaleX = (double)image.getWidth() / zone.getWidth(); - double scaleY = (double)image.getHeight() / zone.getHeight(); + double scaleX = (double)image.getWidth() / width; + double scaleY = (double)image.getHeight() / height; // Fill raster with background color fast int length = (int)(surfaceMax * scaleY) * image.getWidth(); @@ -84,12 +87,12 @@ public static BufferedImage drawSurfaceMap(ZoneInfo zone) { for(int i = 0; i < image.getWidth(); i++) { int surface = surfaceArray[(int)(i / scaleX)]; int distanceToPeak = surface - surfaceMin; - double y = (zone.getHeight() - surface) * scaleY; + double y = (height - surface) * scaleY; double layerHeight = 1.0; int start = image.getHeight() - (int)(y * layerHeight); for(int j = 0; j < depths.length; j++) { - double scale = (double)distanceToPeak / zone.getHeight() / depths.length * (j < 2 ? 2.0 : 0.5); + double scale = (double)distanceToPeak / height / depths.length * (j < 2 ? 2.0 : 0.5); layerHeight -= (depths[j] - scale); int end = j + 1 >= depths.length ? image.getHeight() : image.getHeight() - (int)(y * layerHeight); g2d.setColor(new Color(colors[j > 0 || surface < surfaceCenter ? j : 1])); // Only use snow color if surface is above surface center From 5a1e5fa32d94398b6e203b6ad453ba67a51adcf4 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Wed, 1 Jan 2025 19:56:18 +0100 Subject: [PATCH 141/176] Implement `PositionAchievement` --- .../gameserver/achievement/Achievement.java | 1 + .../achievement/PositionAchievement.java | 36 +++++++++++++++++++ .../brainwine/gameserver/player/Player.java | 9 ++++- 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/achievement/PositionAchievement.java diff --git a/gameserver/src/main/java/brainwine/gameserver/achievement/Achievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/Achievement.java index c1c01548..36abee7d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/achievement/Achievement.java +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/Achievement.java @@ -34,6 +34,7 @@ @Type(name = "Journeyman", value = JourneymanAchievement.class), @Type(name = "ArchitectAchievement", value = ArchitectAchievement.class), @Type(name = "VotingAchievement", value = VotingAchievement.class), + @Type(name = "PositionAchievement", value = PositionAchievement.class), }) @JsonSerialize(using = AchievementSerializer.class) @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/gameserver/src/main/java/brainwine/gameserver/achievement/PositionAchievement.java b/gameserver/src/main/java/brainwine/gameserver/achievement/PositionAchievement.java new file mode 100644 index 00000000..3172915f --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/achievement/PositionAchievement.java @@ -0,0 +1,36 @@ +package brainwine.gameserver.achievement; + +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.Zone; + +public class PositionAchievement extends Achievement { + + @JsonProperty("top") + protected int top = -1; + + @JsonProperty("bottom") + protected int bottom = -1; + + @JsonProperty("left") + protected int left = -1; + + @JsonProperty("right") + protected int right = -1; + + @JsonCreator + public PositionAchievement(@JacksonInject("title") String title) { + super(title); + } + + @Override + public boolean isCompleted(Player player) { + Zone zone = player.getZone(); + int x = left >= 0 ? left : right >= 0 ? zone.getWidth() - right : -1; + int y = top >= 0 ? top : bottom >= 0 ? zone.getHeight() - bottom : -1; + return (x < 0 || Math.abs(player.getBlockX() - x) <= 1) && (y < 0 || Math.abs(player.getBlockY() - y) <= 1); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index bbd87bbb..0bfd5b64 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -26,6 +26,7 @@ import brainwine.gameserver.achievement.Achievement; import brainwine.gameserver.achievement.AchievementManager; import brainwine.gameserver.achievement.JourneymanAchievement; +import brainwine.gameserver.achievement.PositionAchievement; import brainwine.gameserver.command.CommandExecutor; import brainwine.gameserver.dialog.Dialog; import brainwine.gameserver.dialog.DialogListItem; @@ -293,6 +294,12 @@ public void setHealth(float health) { super.setHealth(health); sendMessage(new HealthMessage(health)); } + + @Override + public void blockPositionChanged() { + super.blockPositionChanged(); + updateAchievementProgress(PositionAchievement.class); // TODO check on interval rather than every block position change + } public double getBreathCapacity() { return 15.0 + 1.25 * (getTotalSkillLevel(Skill.SURVIVAL) - 1); @@ -1320,7 +1327,7 @@ public Map getIgnoredHints() { public void updateAchievementProgress(Class achievementType) { List achievementsToCheck = AchievementManager.getAchievements().stream() .filter(achievement -> !hasAchievement(achievement) - && achievementType.isAssignableFrom(achievement.getClass()) + && achievementType == achievement.getClass() && (achievement.getPrevious() == null || hasAchievement(achievement.getPrevious()))) .collect(Collectors.toList()); From a17017363916859a8a1ef5fdfc32b5aad9e0c769 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 2 Jan 2025 01:10:44 +0100 Subject: [PATCH 142/176] Generate proper API tokens for players --- .../brainwine/gameserver/player/Player.java | 22 +++++++-- .../gameserver/player/PlayerConfigFile.java | 6 +++ .../gameserver/player/PlayerManager.java | 19 ++++++++ .../java/brainwine/shared/TokenGenerator.java | 47 +++++++++++++++++++ 4 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 shared/src/main/java/brainwine/shared/TokenGenerator.java diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index 0bfd5b64..f4ace58e 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -102,6 +102,7 @@ public class Player extends Entity implements CommandExecutor { private final String documentId; private String email; private String password; + private String apiToken; private boolean admin; private int experience; private int skillPoints; @@ -159,6 +160,7 @@ protected Player(String documentId, PlayerConfigFile config) { this.name = config.getName(); this.email = config.getEmail(); this.password = config.getPasswordHash(); + this.apiToken = config.getApiToken(); this.admin = config.isAdmin(); this.experience = config.getExperience(); this.skillPoints = config.getSkillPoints(); @@ -484,6 +486,14 @@ public void onZoneChanged() { inventory.moveItemToContainer(jetpack, ContainerType.ACCESSORIES, 0); } + ZoneManager zoneManager = GameServer.getInstance().getZoneManager(); + PlayerManager playerManager = GameServer.getInstance().getPlayerManager(); + + // Issue an API token if the player doesn't have one + if(apiToken == null) { + playerManager.issueApiToken(this); + } + sendMessage(new ConfigurationMessage(id, getClientConfig(), GameConfiguration.getClientConfig(this), zone.getClientConfig(this))); sendMessage(new ZoneStatusMessage(zone.getStatusConfig(this))); zone.sendMachineStatus(this); @@ -527,13 +537,11 @@ public void onZoneChanged() { } // Send social info - PlayerManager playerManager = GameServer.getInstance().getPlayerManager(); sendMessage(new FollowMessage(followees.stream().map(playerManager::getPlayerById).filter(Objects::nonNull).collect(Collectors.toList()), 0)); sendMessage(new FollowMessage(followers.stream().map(playerManager::getPlayerById).filter(Objects::nonNull).collect(Collectors.toList()), 1)); sendMessage(new EventMessage("socialInfoReady", null)); // Clear invalid bookmarks - ZoneManager zoneManager = GameServer.getInstance().getZoneManager(); bookmarkedZones.removeIf(bookmark -> zoneManager.getZone(bookmark) == null || !zoneManager.getZone(bookmark).canJoin(this)); // Misc stuff @@ -997,6 +1005,14 @@ protected String getPassword() { return password; } + protected void setApiToken(String apiToken) { + this.apiToken = apiToken; + } + + protected String getApiToken() { + return apiToken; + } + protected void clearAuthTokens() { authTokens.clear(); } @@ -1664,7 +1680,7 @@ public Map getClientConfig() { config.put("deaths", statistics.getDeaths()); config.put("appearance", appearance); config.put("settings", settings); - config.put("api_token", documentId); // Use document ID for now + config.put("api_token", apiToken); return config; } } diff --git a/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java index 5c739b5e..c85d2bba 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerConfigFile.java @@ -22,6 +22,7 @@ public class PlayerConfigFile { private String name; private String email; private String passwordHash; + private String apiToken; private Zone currentZone; private boolean admin; private int experience; @@ -49,6 +50,7 @@ public PlayerConfigFile(Player player) { this.name = player.getName(); this.email = player.getEmail(); this.passwordHash = player.getPassword(); + this.apiToken = player.getApiToken(); this.currentZone = player.getZone(); this.admin = player.isAdmin(); this.experience = player.getExperience(); @@ -89,6 +91,10 @@ public String getPasswordHash() { return passwordHash; } + public String getApiToken() { + return apiToken; + } + public Zone getCurrentZone() { return currentZone; } diff --git a/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java index 163b89ba..cb5a1937 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java @@ -18,6 +18,7 @@ import brainwine.gameserver.server.pipeline.Connection; import brainwine.shared.JsonHelper; +import brainwine.shared.TokenGenerator; public class PlayerManager { @@ -26,6 +27,7 @@ public class PlayerManager { private static final Logger logger = LogManager.getLogger(); private final Map playersById = new HashMap<>(); private final Map playersByName = new HashMap<>(); + private final Map apiTokens = new HashMap<>(); private final List onlinePlayers = new ArrayList<>(); public PlayerManager() { @@ -112,6 +114,23 @@ public String login(String name, String password) { return authToken; } + protected void issueApiToken(Player player) { + String apiToken = TokenGenerator.generateToken(10, apiTokens::containsKey); + String currentToken = player.getApiToken(); + + if(apiToken == null) { + player.notify("Oops, we couldn't issue an API token for you.", NotificationType.SYSTEM); + return; + } + + if(currentToken != null && !apiTokens.remove(currentToken, player)) { + logger.warn("Could not unindex API token {} for player {}", currentToken, player.getDocumentId()); + } + + player.setApiToken(apiToken); + apiTokens.put(apiToken, player); + } + public boolean verifyAuthToken(String name, String authToken) { Player player = getPlayer(name); diff --git a/shared/src/main/java/brainwine/shared/TokenGenerator.java b/shared/src/main/java/brainwine/shared/TokenGenerator.java new file mode 100644 index 00000000..e666c9c9 --- /dev/null +++ b/shared/src/main/java/brainwine/shared/TokenGenerator.java @@ -0,0 +1,47 @@ +package brainwine.shared; + +import java.security.SecureRandom; +import java.util.function.Function; + +/** + * Utility for generating alphanumeric tokens. + */ +public class TokenGenerator { + + public static final String CHARTABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"; + public static final int MAX_ATTEMPTS = 100; + private static final SecureRandom secureRandom = new SecureRandom(); + + /** + * Attempts to generate a unique token by testing for duplicates with the caller-specified function. + * If no unique token is generated within 100 attempts, the function gives up and returns {@code null} instead. + */ + public static String generateToken(int size, Function dupCheck) { + int attempts = MAX_ATTEMPTS; + + while(attempts > 0) { + String token = generateToken(size); + + if(!dupCheck.apply(token)) { + return token; + } + + attempts--; + } + + return null; + } + + /** + * Securely generates a token of the specified size. + */ + public static String generateToken(int size) { + char[] chars = new char[size]; + + for(int i = 0; i < size; i++) { + chars[i] = CHARTABLE.charAt(secureRandom.nextInt(CHARTABLE.length())); + } + + return new String(chars); + } +} From de690c504746aeb8d34310f1da159948b914d5f9 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 2 Jan 2025 01:41:29 +0100 Subject: [PATCH 143/176] Handle API token in zone search requests --- .../main/java/brainwine/api/DataFetcher.java | 2 +- .../brainwine/api/DefaultDataFetcher.java | 6 +++--- .../java/brainwine/api/PortalService.java | 20 +++++++------------ .../gameserver/player/PlayerManager.java | 8 ++++++++ .../server/models/ZoneSearchData.java | 2 +- .../java/brainwine/DirectDataFetcher.java | 15 +++++++------- 6 files changed, 28 insertions(+), 25 deletions(-) diff --git a/api/src/main/java/brainwine/api/DataFetcher.java b/api/src/main/java/brainwine/api/DataFetcher.java index b32ea475..a5f2737a 100644 --- a/api/src/main/java/brainwine/api/DataFetcher.java +++ b/api/src/main/java/brainwine/api/DataFetcher.java @@ -10,8 +10,8 @@ public interface DataFetcher { public String registerPlayer(String name); public String login(String name, String password); public String fetchPlayerName(String name); + public String fetchPlayerId(String apiToken); public boolean verifyAuthToken(String name, String token); - public boolean verifyApiToken(String apiToken); public ZoneInfo getZoneInfo(String nameOrId); public Collection fetchZoneInfo(); public Collection fetchRecentZoneInfo(String apiToken); diff --git a/api/src/main/java/brainwine/api/DefaultDataFetcher.java b/api/src/main/java/brainwine/api/DefaultDataFetcher.java index a9d65e44..27d224cf 100644 --- a/api/src/main/java/brainwine/api/DefaultDataFetcher.java +++ b/api/src/main/java/brainwine/api/DefaultDataFetcher.java @@ -29,12 +29,12 @@ public String fetchPlayerName(String name) { } @Override - public boolean verifyAuthToken(String name, String token) { + public String fetchPlayerId(String apiToken) { throw exception; } - + @Override - public boolean verifyApiToken(String apiToken) { + public boolean verifyAuthToken(String name, String token) { throw exception; } diff --git a/api/src/main/java/brainwine/api/PortalService.java b/api/src/main/java/brainwine/api/PortalService.java index b2e5cb03..07f41f73 100644 --- a/api/src/main/java/brainwine/api/PortalService.java +++ b/api/src/main/java/brainwine/api/PortalService.java @@ -55,16 +55,9 @@ private void handleException(Exception exception, Context ctx) { /** * Handler function for map render requests. - * TODO throttle + * TODO add throttle & ownership privacy */ private void handleMapRequest(Context ctx) throws IOException { - String apiToken = ctx.queryParam("api_token"); - - if(apiToken == null || !dataFetcher.verifyApiToken(apiToken)) { - error(ctx, "A valid api token is required for this request."); - return; - } - ZoneInfo zone = dataFetcher.getZoneInfo(ctx.pathParam("zone")); if(zone == null) { @@ -109,9 +102,10 @@ private void handleMapRequest(Context ctx) throws IOException { */ private void handleZoneSearch(Context ctx) { String apiToken = ctx.queryParam("api_token"); + String playerId = apiToken != null ? dataFetcher.fetchPlayerId(apiToken) : null; - if(apiToken == null || !dataFetcher.verifyApiToken(apiToken)) { - error(ctx, "A valid api token is required for this request."); + if(playerId == null && (ctx.queryParam("account") == null || ctx.queryParam("residency") == null)) { + error(ctx, "Request contains one or more parameters that require a valid API token."); return; } @@ -120,7 +114,7 @@ private void handleZoneSearch(Context ctx) { final List zones = account == null ? (List)dataFetcher.fetchZoneInfo() : account.equals("recent") ? (List)dataFetcher.fetchRecentZoneInfo(apiToken) : account.equals("bookmarked") ? (List)dataFetcher.fetchBookmarkedZoneInfo(apiToken) : new ArrayList<>(); - zones.removeIf(zone -> zone.isPrivate() && !apiToken.equals(zone.getOwner()) && !zone.getMembers().contains(apiToken)); + zones.removeIf(zone -> zone.isPrivate() && (playerId == null || (!playerId.equals(zone.getOwner()) && !zone.getMembers().contains(playerId)))); handleQueryParam(ctx, "name", String.class, name -> { zones.removeIf(zone -> !zone.getName().toLowerCase().contains(name.toLowerCase())); @@ -145,10 +139,10 @@ private void handleZoneSearch(Context ctx) { handleQueryParam(ctx, "residency", String.class, residency -> { switch(residency) { case "owned": - zones.removeIf(zone -> !apiToken.equals(zone.getOwner())); + zones.removeIf(zone -> !playerId.equals(zone.getOwner())); break; case "member": - zones.removeIf(zone -> !zone.getMembers().contains(apiToken)); + zones.removeIf(zone -> !zone.getMembers().contains(playerId)); break; default: zones.clear(); diff --git a/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java index cb5a1937..ef1d455c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java @@ -63,6 +63,10 @@ private void loadPlayer(File file) { playersById.put(id, player); playersByName.put(name.toLowerCase(), player); + + if(player.getApiToken() != null) { + apiTokens.put(player.getApiToken(), player); + } } catch (Exception e) { logger.error(SERVER_MARKER, "Could not load configuration for player id {}", id, e); } @@ -192,6 +196,10 @@ public Player getPlayerById(String id) { return playersById.get(id); } + public Player getPlayerByApiToken(String apiToken) { + return apiTokens.get(apiToken); + } + public Collection getPlayers() { return playersById.values(); } diff --git a/gameserver/src/main/java/brainwine/gameserver/server/models/ZoneSearchData.java b/gameserver/src/main/java/brainwine/gameserver/server/models/ZoneSearchData.java index e5435141..19bfba43 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/models/ZoneSearchData.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/models/ZoneSearchData.java @@ -19,7 +19,7 @@ public class ZoneSearchData { public final int explorationProgress; public final Biome biome; public final String status; - public final String accessibility; // 'a' = all, 'p' = premium + public final String accessibility; // 'a' = all, 'p' = premium, 'i' = inaccessible public final int protectionLevel; public final String scenario; // Market, PvP, etc. diff --git a/src/main/java/brainwine/DirectDataFetcher.java b/src/main/java/brainwine/DirectDataFetcher.java index d25c92dc..5b089878 100644 --- a/src/main/java/brainwine/DirectDataFetcher.java +++ b/src/main/java/brainwine/DirectDataFetcher.java @@ -43,17 +43,18 @@ public String fetchPlayerName(String name) { Player player = playerManager.getPlayer(name); return player == null ? null : player.getName(); } + + @Override + public String fetchPlayerId(String apiToken) { + Player player = playerManager.getPlayerByApiToken(apiToken); + return player == null ? null : player.getDocumentId(); + } @Override public boolean verifyAuthToken(String name, String token) { return playerManager.verifyAuthToken(name, token); } - @Override - public boolean verifyApiToken(String apiToken) { - return true; // TODO - } - @Override public ZoneInfo getZoneInfo(String nameOrId) { Zone zone = zoneManager.getZoneByName(nameOrId); @@ -82,13 +83,13 @@ public Collection fetchZoneInfo() { @Override public Collection fetchRecentZoneInfo(String apiToken) { - Player player = playerManager.getPlayerById(apiToken); + Player player = playerManager.getPlayerByApiToken(apiToken); return player == null ? new ArrayList<>() : createZoneInfo(player.getRecentZones()); } @Override public Collection fetchBookmarkedZoneInfo(String apiToken) { - Player player = playerManager.getPlayerById(apiToken); + Player player = playerManager.getPlayerByApiToken(apiToken); return player == null ? new ArrayList<>() : createZoneInfo(player.getBookmarkedZones()); } From 81e4df4ed44515ed6493ec5068de8567b830674e Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 2 Jan 2025 02:13:00 +0100 Subject: [PATCH 144/176] Add `/api` command --- .../gameserver/command/ApiCommand.java | 43 +++++++++++++++++++ .../brainwine/gameserver/player/Player.java | 2 +- .../gameserver/player/PlayerManager.java | 5 ++- 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/command/ApiCommand.java diff --git a/gameserver/src/main/java/brainwine/gameserver/command/ApiCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/ApiCommand.java new file mode 100644 index 00000000..4495d3f8 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/command/ApiCommand.java @@ -0,0 +1,43 @@ +package brainwine.gameserver.command; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.dialog.Dialog; +import brainwine.gameserver.dialog.DialogSection; +import brainwine.gameserver.player.Player; + +@CommandInfo(name = "api", description = "Lets you configure your API settings.") +public class ApiCommand extends Command { + + @Override + public void execute(CommandExecutor executor, String[] args) { + Player player = ((Player)executor); + + // Create & show settings dialog + Dialog dialog = new Dialog(); + dialog.setTitle("API Settings"); + dialog.addSection(new DialogSection().setTitle("Your API token:").setText(String.format(player.isV3() ? "%s" : "%s", player.getApiToken())).setTextColor("ffd95f")); + dialog.addSection(new DialogSection().setText("Generate new API token").setChoice("reissue")); + player.showDialog(dialog, input -> { + // Handle cancellation + if(input.length == 0 || (input.length == 1 && "cancel".equals(input[0]))) { + return; + } + + if("reissue".equals(input[0])) { + if(GameServer.getInstance().getPlayerManager().issueApiToken(player)) { + player.kick("API token reissued", true); + } + } + }); + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/api"; + } + + @Override + public boolean canExecute(CommandExecutor executor) { + return executor instanceof Player; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index f4ace58e..f7766719 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -1009,7 +1009,7 @@ protected void setApiToken(String apiToken) { this.apiToken = apiToken; } - protected String getApiToken() { + public String getApiToken() { return apiToken; } diff --git a/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java index ef1d455c..c6b2f487 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java @@ -118,13 +118,13 @@ public String login(String name, String password) { return authToken; } - protected void issueApiToken(Player player) { + public boolean issueApiToken(Player player) { String apiToken = TokenGenerator.generateToken(10, apiTokens::containsKey); String currentToken = player.getApiToken(); if(apiToken == null) { player.notify("Oops, we couldn't issue an API token for you.", NotificationType.SYSTEM); - return; + return false; } if(currentToken != null && !apiTokens.remove(currentToken, player)) { @@ -133,6 +133,7 @@ protected void issueApiToken(Player player) { player.setApiToken(apiToken); apiTokens.put(apiToken, player); + return true; } public boolean verifyAuthToken(String name, String authToken) { From 69afade979261c000d943ea4786820ad4f7659f0 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 2 Jan 2025 02:18:22 +0100 Subject: [PATCH 145/176] Oops --- api/src/main/java/brainwine/api/PortalService.java | 2 +- .../main/java/brainwine/gameserver/command/ApiCommand.java | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/brainwine/api/PortalService.java b/api/src/main/java/brainwine/api/PortalService.java index 07f41f73..feb015fa 100644 --- a/api/src/main/java/brainwine/api/PortalService.java +++ b/api/src/main/java/brainwine/api/PortalService.java @@ -104,7 +104,7 @@ private void handleZoneSearch(Context ctx) { String apiToken = ctx.queryParam("api_token"); String playerId = apiToken != null ? dataFetcher.fetchPlayerId(apiToken) : null; - if(playerId == null && (ctx.queryParam("account") == null || ctx.queryParam("residency") == null)) { + if(playerId == null && (ctx.queryParam("account") != null || ctx.queryParam("residency") != null)) { error(ctx, "Request contains one or more parameters that require a valid API token."); return; } diff --git a/gameserver/src/main/java/brainwine/gameserver/command/ApiCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/ApiCommand.java index 4495d3f8..6f4635f0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/ApiCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/ApiCommand.java @@ -23,10 +23,8 @@ public void execute(CommandExecutor executor, String[] args) { return; } - if("reissue".equals(input[0])) { - if(GameServer.getInstance().getPlayerManager().issueApiToken(player)) { - player.kick("API token reissued", true); - } + if("reissue".equals(input[0]) && GameServer.getInstance().getPlayerManager().issueApiToken(player)) { + player.kick("API token changed.", true); } }); } From fbbdfbe4877bbedd7d2eacb9b5414d67b8de6fd0 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 2 Jan 2025 23:02:20 +0100 Subject: [PATCH 146/176] Add zone entry codes --- .../command/world/WorldEnterCommand.java | 57 +++++++++++++++++++ .../command/world/WorldInfoCommand.java | 2 +- .../command/world/WorldRecodeCommand.java | 27 +++++++++ .../java/brainwine/gameserver/zone/Zone.java | 14 +++++ .../gameserver/zone/ZoneConfigFile.java | 8 +++ .../gameserver/zone/ZoneManager.java | 32 +++++++++++ 6 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/command/world/WorldEnterCommand.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/command/world/WorldRecodeCommand.java diff --git a/gameserver/src/main/java/brainwine/gameserver/command/world/WorldEnterCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldEnterCommand.java new file mode 100644 index 00000000..21ca9c40 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldEnterCommand.java @@ -0,0 +1,57 @@ +package brainwine.gameserver.command.world; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.Zone; + +@CommandInfo(name = "wenter", description = "Enter a world with a specific entry code.") +public class WorldEnterCommand extends Command { + + @Override + public void execute(CommandExecutor executor, String[] args) { + if(!checkArgumentCount(executor, args, 1)) { + return; + } + + Player player = (Player)executor; + String entryCode = args[0]; + Zone zone = GameServer.getInstance().getZoneManager().getZoneByEntryCode(entryCode); + + // Check if zone exists + if(zone == null) { + player.notify("Can't find a zone for that code."); + return; + } + + // Check if player is already a member of the target zone + if(zone.isOwner(player) || zone.isMember(player)) { + player.notify(String.format("You're already a member of %s.\nFind yourself a teleporter.", zone.getName())); + return; + } + + // Add player to zone + if(!zone.isOwned()) { + zone.setOwner(player); + } else { + zone.addMember(player); + } + + // Send player to zone if they're not there already + if(zone != player.getZone()) { + player.changeZone(zone); + } + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/wenter "; + } + + @Override + public boolean canExecute(CommandExecutor executor) { + return executor instanceof Player; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/command/world/WorldInfoCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldInfoCommand.java index 58ba7001..bc322cee 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/world/WorldInfoCommand.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldInfoCommand.java @@ -27,7 +27,7 @@ public void execute(Zone zone, Player player, String[] args) { .addSection(new DialogSection().setTitle("World Info")) .addSection(new DialogSection().setText(" ")) .addSection(new DialogSection().setText(player.isV3() ? "Entry Code" : "Entry Code").setTextColor("4d5b82")) - .addSection(new DialogSection().setText("Entry codes are currently unavailable.")) + .addSection(new DialogSection().setText(zone.hasEntryCode() ? zone.getEntryCode() : "Use /wrecode to generate an entry code")) .addSection(new DialogSection().setText(" ")) .addSection(new DialogSection().setText(player.isV3() ? "Members" : "Members").setTextColor("4d5b82")) .addSection(new DialogSection().setText(memberNames.isEmpty() ? "None :(" : memberNames.toString().replaceAll("[\\[+\\]]", ""))); diff --git a/gameserver/src/main/java/brainwine/gameserver/command/world/WorldRecodeCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldRecodeCommand.java new file mode 100644 index 00000000..402d8824 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/command/world/WorldRecodeCommand.java @@ -0,0 +1,27 @@ +package brainwine.gameserver.command.world; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.zone.Zone; + +@CommandInfo(name = "wrecode", description = "Issues a new entry code for your private world.") +public class WorldRecodeCommand extends WorldCommand { + + @Override + public void execute(Zone zone, Player player, String[] args) { + // Try to generate a new entry code + if(!GameServer.getInstance().getZoneManager().issueEntryCode(zone)) { + player.notify("Unable to change the entry code, please try again."); + return; + } + + player.notify(String.format("Your world entry code has been changed to %s.", zone.getEntryCode())); + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/wrecode"; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index e0a421a3..ce9ddb32 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -84,6 +84,7 @@ public class Zone { private boolean isPrivate; private boolean isProtected; private boolean pvp; + private String entryCode; private String owner; private final ChunkManager chunkManager; private final SteamManager steamManager; @@ -120,6 +121,7 @@ protected Zone(String documentId, ZoneConfigFile config, ZoneDataFile data) { steamManager.setData(data.getSteamData()); machineManager.loadData(config); pendingSunlight.addAll(data.getPendingSunlight()); + entryCode = config.getEntryCode(); owner = config.getOwner(); members.addAll(config.getMembers()); actionHistory.putAll(config.getActionHistory()); @@ -1746,6 +1748,18 @@ public boolean isPvp() { return pvp; } + protected void setEntryCode(String entryCode) { + this.entryCode = entryCode; + } + + public boolean hasEntryCode() { + return entryCode != null; + } + + public String getEntryCode() { + return entryCode; + } + public boolean isOwner(Player player) { return isOwned() && player.getDocumentId().equals(owner); } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java index d1e29184..f45cddb9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneConfigFile.java @@ -41,6 +41,9 @@ public class ZoneConfigFile { @JsonSetter(nulls = Nulls.SKIP) private boolean pvp; + @JsonSetter(nulls = Nulls.SKIP) + private String entryCode; + @JsonSetter(nulls = Nulls.SKIP) private String owner; @@ -77,6 +80,7 @@ public ZoneConfigFile(Zone zone) { this.isPrivate = zone.isPrivate(); this.isProtected = zone.isProtected(); this.pvp = zone.isPvp(); + this.entryCode = zone.getEntryCode(); this.owner = zone.getOwner(); this.members = zone.getMembers(); this.discoveredParts = zone.getDiscoveredParts(); @@ -116,6 +120,10 @@ public boolean isPvp() { return pvp; } + public String getEntryCode() { + return entryCode; + } + public String getOwner() { return owner; } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java index 52a318e7..afa056d7 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ZoneManager.java @@ -29,6 +29,7 @@ import brainwine.gameserver.util.ZipUtils; import brainwine.gameserver.zone.gen.ZoneGenerator; import brainwine.shared.JsonHelper; +import brainwine.shared.TokenGenerator; public class ZoneManager { @@ -38,6 +39,7 @@ public class ZoneManager { private final File dataDir = new File("zones"); private Map zones = new HashMap<>(); private Map zonesByName = new HashMap<>(); + private Map entryCodes = new HashMap<>(); private long lastZoneGenerationTime = System.currentTimeMillis(); private boolean generatingZone = false; @@ -190,6 +192,10 @@ public void addZone(Zone zone) { zones.put(id, zone); zonesByName.put(name.toLowerCase(), zone); + + if(zone.hasEntryCode()) { + entryCodes.put(zone.getEntryCode(), zone); + } } /** @@ -222,6 +228,28 @@ public boolean renameZone(Zone zone, String name) { return true; } + /** + * Generates a new entry code for the specified zone and re-indexes it. + * + * @return {@code true} if the entry code was generated successfully, otherwise {@code false}. + */ + public boolean issueEntryCode(Zone zone) { + String entryCode = String.format("z%s", TokenGenerator.generateToken(6, entryCodes::containsKey)); + String currentCode = zone.getEntryCode(); + + if(entryCode == null) { + return false; + } + + if(currentCode != null && !entryCodes.remove(currentCode, zone)) { + logger.warn(SERVER_MARKER, "Could not unindex entry code {} for zone {}", currentCode, zone.getDocumentId()); + } + + zone.setEntryCode(entryCode); + entryCodes.put(entryCode, zone); + return true; + } + public Zone getZone(String id) { return zones.get(id); } @@ -234,6 +262,10 @@ public Zone getZoneByName(String name) { return zonesByName.get(name.toLowerCase()); } + public Zone getZoneByEntryCode(String entryCode) { + return entryCodes.get(entryCode); + } + /** * @return A public, non-owned, recently-generated temperate world (with players if possible) or {@code null} if no such world exists. */ From 35c44f636b0705be57b2c89b373e7b0b33b5cc61 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 2 Jan 2025 23:07:56 +0100 Subject: [PATCH 147/176] Fix some missing logger markers --- .../java/brainwine/gameserver/command/CommandManager.java | 2 +- .../src/main/java/brainwine/gameserver/player/Player.java | 4 +++- .../main/java/brainwine/gameserver/player/PlayerManager.java | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/command/CommandManager.java b/gameserver/src/main/java/brainwine/gameserver/command/CommandManager.java index d53fb05f..b11de02d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/command/CommandManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/command/CommandManager.java @@ -110,7 +110,7 @@ public static void registerCommand(Class type) { try { command = type.getConstructor().newInstance(); } catch(ReflectiveOperationException e) { - logger.error("Failed to not instantiate command '{}'", type.getSimpleName(), e); + logger.error(SERVER_MARKER, "Failed to instantiate command '{}'", type.getSimpleName(), e); return; } diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index f7766719..c0ca69c9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -1,5 +1,7 @@ package brainwine.gameserver.player; +import static brainwine.shared.LogMarkers.SERVER_MARKER; + import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -678,7 +680,7 @@ public void handleDialogInput(int id, Object[] input) { try { handler.accept(input); } catch(Exception e) { - logger.error("An error occured while handling dialog input", e); + logger.error(SERVER_MARKER, "An error occured while handling dialog input", e); notify("Oops! There was a problem processing your input."); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java b/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java index c6b2f487..5f2eb7de 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/PlayerManager.java @@ -128,7 +128,7 @@ public boolean issueApiToken(Player player) { } if(currentToken != null && !apiTokens.remove(currentToken, player)) { - logger.warn("Could not unindex API token {} for player {}", currentToken, player.getDocumentId()); + logger.warn(SERVER_MARKER, "Could not unindex API token {} for player {}", currentToken, player.getDocumentId()); } player.setApiToken(apiToken); @@ -157,7 +157,7 @@ public boolean verifyAuthToken(String name, String authToken) { public void changePlayerName(Player player, String name) { if(playersByName.containsKey(name)) { - logger.warn("Tried to rename player {} to already existing name {}", player.getDocumentId(), name); + logger.warn(SERVER_MARKER, "Tried to rename player {} to already existing name {}", player.getDocumentId(), name); return; } From 5d3668ed092c595c51c68c71780cc4e42849cd43 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Fri, 3 Jan 2025 04:04:09 +0100 Subject: [PATCH 148/176] Add auto rotation support for large block sizes --- .../server/requests/BlockPlaceRequest.java | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java index 6be288e4..96e542d2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockPlaceRequest.java @@ -113,16 +113,7 @@ public void process(Player player) { if(layer == Layer.LIQUID) { mod = 5; } else if(item.getMod() == ModType.ROTATION && !item.isMirrorable()) { - // Automatically orient rotatable blocks based on adjacent block - if(zone.isChunkLoaded(x, y + 1) && zone.getBlock(x, y + 1).getFrontItem().isWhole()) { - mod = 0; - } else if(zone.isChunkLoaded(x, y - 1) && zone.getBlock(x, y - 1).getFrontItem().isWhole()) { - mod = 2; - } else if(zone.isChunkLoaded(x - 1, y) && zone.getBlock(x - 1, y).getFrontItem().isWhole()) { - mod = 1; - } else if(zone.isChunkLoaded(x + 1, y) && zone.getBlock(x + 1, y).getFrontItem().isWhole()) { - mod = 3; - } + mod = findRotationMod(zone, x, y, item.getBlockWidth(), item.getBlockHeight()); } zone.updateBlock(x, y, layer, item, mod, player); @@ -148,6 +139,31 @@ public void process(Player player) { } } + /** + * Automatically finds a suitable rotation mod based on adjacent blocks. + * Priority order is bottom -> top -> left -> right. + */ + private int findRotationMod(Zone zone, int x, int y, int width, int height) { + boolean bottom = true; + boolean top = true; + boolean left = true; + boolean right = true; + + // Check top and bottom + for(int i = 0; i < width; i++) { + bottom &= zone.isChunkLoaded(x + i, y + 1) && zone.getBlock(x + i, y + 1).getFrontItem().isWhole(); + top &= zone.isChunkLoaded(x + i, y - height) && zone.getBlock(x + i, y - height).getFrontItem().isWhole(); + } + + // Check left and right + for(int i = 0; i < height; i++) { + left &= zone.isChunkLoaded(x - 1, y - i) && zone.getBlock(x - 1, y - i).getFrontItem().isWhole(); + right &= zone.isChunkLoaded(x + width, y - i) && zone.getBlock(x + width, y - i).getFrontItem().isWhole(); + } + + return bottom ? 0 : top ? 2 : left ? 1 : right ? 3 : 0; + } + private void processTrapping(Zone zone, Player player) { // Check bounds if(x <= 0 || x + 1 >= zone.getWidth() || y <= 0 || y + 1 >= zone.getHeight()) { From d43d5072e300030eaa27f0235200129cac72ccdc Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 4 Jan 2025 00:42:04 +0100 Subject: [PATCH 149/176] Store products in ordered map --- .../src/main/java/brainwine/gameserver/shop/ShopManager.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/shop/ShopManager.java b/gameserver/src/main/java/brainwine/gameserver/shop/ShopManager.java index c530aaaa..84f7954a 100644 --- a/gameserver/src/main/java/brainwine/gameserver/shop/ShopManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/shop/ShopManager.java @@ -5,7 +5,6 @@ import java.net.URL; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; @@ -31,7 +30,7 @@ public class ShopManager { private static final Logger logger = LogManager.getLogger(); private static final Map sections = new LinkedHashMap<>(); - private static final Map products = new HashMap<>(); + private static final Map products = new LinkedHashMap<>(); public static void loadShopData() { logger.info(SERVER_MARKER, "Loading shop data ..."); @@ -42,7 +41,7 @@ public static void loadShopData() { URL url = ResourceFinder.getResourceUrl("shop.json"); Map data = JsonHelper.readValue(url, new TypeReference>(){}); sections.putAll(JsonHelper.readValue(data.getOrDefault("sections", Collections.emptyMap()), new TypeReference>(){})); - products.putAll(JsonHelper.readValue(data.getOrDefault("products", Collections.emptyMap()), new TypeReference>(){})); + products.putAll(JsonHelper.readValue(data.getOrDefault("products", Collections.emptyMap()), new TypeReference>(){})); } catch(Exception e) { logger.error(SERVER_MARKER, "Could not load shop data", e); return; From d650b7531ec00234674ef83635aa9e3facb15ab9 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 4 Jan 2025 00:42:39 +0100 Subject: [PATCH 150/176] Nerf player health regen --- .../java/brainwine/gameserver/player/Player.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index c0ca69c9..361c5890 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -96,9 +96,9 @@ public class Player extends Entity implements CommandExecutor { public static final int HEARTBEAT_TIMEOUT = 30000; public static final int MAX_AUTH_TOKENS = 3; public static final int TRACKED_ENTITY_UPDATE_INTERVAL = 100; - public static final int REGEN_NO_DAMAGE_TIME = 10000; public static final float ENTITY_VISIBILITY_RANGE = 40; - public static final float BASE_REGEN_AMOUNT = 0.1F; + public static final int BASE_REGEN_INTERVAL = 30000; + public static final float BASE_REGEN_AMOUNT = 1.0F / 3.0F; private static final Logger logger = LogManager.getLogger(); private static int dialogDiscriminator; private final String documentId; @@ -153,6 +153,7 @@ public class Player extends Entity implements CommandExecutor { private long lastHeartbeat; private long lastTrackedEntityUpdate; private long lastLandmarkVoteAt; + private long lastHealthRegenAt; private Zone nextZone; private Connection connection; @@ -229,9 +230,10 @@ public void tick(float deltaTime) { } } - // Regenerate health out of combat - if(!isDead() && now >= lastDamagedAt + REGEN_NO_DAMAGE_TIME) { - heal(BASE_REGEN_AMOUNT * deltaTime); + // Regenerate health out of combat + if(!isDead() && now >= Math.max(lastHealthRegenAt, lastDamagedAt) + BASE_REGEN_INTERVAL) { + heal(BASE_REGEN_AMOUNT); + lastHealthRegenAt = now; } if(!isDead()) { From ecee966782e7bc495cfc9472c4b73792078c6dc3 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 4 Jan 2025 03:01:23 +0100 Subject: [PATCH 151/176] Turns out, there *is* such an item... --- .../src/main/java/brainwine/gameserver/zone/Zone.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index ce9ddb32..cad7eae2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -615,17 +615,21 @@ public boolean isBlockProtected(int x, int y, Player player) { } public boolean isBlockProtected(int x, int y, Player player, Collection fieldBlocks) { + // Check bounds + if(!areCoordinatesInBounds(x, y)) { + return true; + } + // Check protection at zone level if(player != null && isProtected(player)) { return true; } + Item frontItem = getBlock(x, y).getFrontItem(); // TODO can load chunks! MetaBlock metaBlock = getMetaBlock(x, y); // Check block owner if it has a field - // TODO this will cause issues for field blocks that have no metadata (though conveniently, no such items exist by default) - // but checking the item here would cause some functions to gain chunk loading privileges. - if(metaBlock != null && metaBlock.getItem().hasField() && !metaBlock.isOwnedBy(player)) { + if(frontItem.hasField() && (metaBlock == null || !metaBlock.isOwnedBy(player))) { return true; } From 7c71ea028bd95e434a0aa2de405e57720013b435 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 4 Jan 2025 04:52:33 +0100 Subject: [PATCH 152/176] Remember player positions --- .../brainwine/gameserver/player/Player.java | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index 361c5890..89aeb873 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -138,8 +138,8 @@ public class Player extends Entity implements CommandExecutor { private double breath = 1.0; private double thirst; private double cold; - private int spawnX; - private int spawnY; + private int spawnX = -1; + private int spawnY = -1; private int teleportX; private int teleportY; private boolean stealth; @@ -447,29 +447,26 @@ public Map getStatusConfig() { * Called by {@link Zone#addEntity(Entity)} when the player is added to it. */ public void onZoneChanged() { - // Set spawn location - if(customSpawn) { - x = spawnX; - y = spawnY; - } - - MetaBlock spawn = zone.getRandomSpawnBlock(); - - if(spawn == null) { - spawnX = zone.getWidth() / 2; - spawnY = 2; - } else { - spawnX = spawn.getX() + 1; - spawnY = spawn.getY(); + // Find a random new spawn if one isn't assigned yet + if(spawnX == -1 || spawnY == -1) { + MetaBlock spawn = zone.getRandomSpawnBlock(); + + if(spawn == null) { + spawnX = zone.getWidth() / 2; + spawnY = 2; + } else { + spawnX = spawn.getX() + 1; + spawnY = spawn.getY(); + } } + // Set the player's location to their spawn location if no custom spawn is set if(!customSpawn) { x = spawnX; y = spawnY; + customSpawn = true; // Remember position until zone changes } - customSpawn = false; - // Set skills for new players for(Skill skill : Skill.values()) { if(!skills.containsKey(skill)) { @@ -646,9 +643,11 @@ public void changeZone(Zone zone) { public void changeZone(Zone zone, int x, int y) { changingZones = true; nextZone = zone; - spawnX = x; - spawnY = y; + spawnX = -1; + spawnY = -1; customSpawn = x != -1 && y != -1; + this.x = x; + this.y = y; sendMessage(new EventMessage("playerWillChangeZone", null)); kick("Teleporting...", true); } From f9e05ebc8f7c4d7abc62b7becbf20dd05a0f23cf Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 4 Jan 2025 07:39:14 +0100 Subject: [PATCH 153/176] Rescue players who are out of bounds somehow --- .../src/main/java/brainwine/gameserver/player/Player.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index 89aeb873..b5668c52 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -460,8 +460,8 @@ public void onZoneChanged() { } } - // Set the player's location to their spawn location if no custom spawn is set - if(!customSpawn) { + // Set the player's location to their spawn location if no custom spawn is set or if they are out of bounds + if(!customSpawn || !zone.areCoordinatesInBounds(blockX, blockY)) { x = spawnX; y = spawnY; customSpawn = true; // Remember position until zone changes From 5abc98f4e665042269a4dc9a6cefc1d448097a99 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sat, 4 Jan 2025 07:40:06 +0100 Subject: [PATCH 154/176] Refactor `onZoneChanged()` to `onZoneEntered()` --- .../src/main/java/brainwine/gameserver/player/Player.java | 2 +- .../src/main/java/brainwine/gameserver/zone/EntityManager.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index b5668c52..2b84571d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -446,7 +446,7 @@ public Map getStatusConfig() { /** * Called by {@link Zone#addEntity(Entity)} when the player is added to it. */ - public void onZoneChanged() { + public void onZoneEntered() { // Find a random new spawn if one isn't assigned yet if(spawnX == -1 || spawnY == -1) { MetaBlock spawn = zone.getRandomSpawnBlock(); diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java index 0c2ff5cf..5d1fd6d2 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java @@ -294,7 +294,7 @@ public void addEntity(Entity entity) { if(entity instanceof Player) { Player player = (Player)entity; - player.onZoneChanged(); + player.onZoneEntered(); players.put(entityId, player); playersByName.put(player.getName().toLowerCase(), player); player.sendMessageToPeers(new EntityStatusMessage(player, EntityStatus.ENTERING)); From 72e37ea263787e1d47642dfe200dbad41dd8af87 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 5 Jan 2025 19:04:48 +0100 Subject: [PATCH 155/176] Generate bedrock at top of world in`FILLED` terrain types --- .../brainwine/gameserver/zone/gen/GeneratorContext.java | 2 +- .../java/brainwine/gameserver/zone/gen/ZoneGenerator.java | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/GeneratorContext.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/GeneratorContext.java index 4dd8cc35..dac32a47 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/GeneratorContext.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/GeneratorContext.java @@ -56,7 +56,7 @@ public List getCaves() { public boolean placePrefab(Prefab prefab, int x, int y) { x = Math.max(1, Math.min(x, getWidth() - prefab.getWidth() - 1)); - y = Math.max(1, Math.min(y, getHeight() - prefab.getHeight() - 3)); + y = Math.max(3, Math.min(y, getHeight() - prefab.getHeight() - 3)); if(!willPrefabOverlap(prefab, x, y)) { boolean mirrored = random.nextBoolean(); diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java index 3f5d18f6..88ddc2a1 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/ZoneGenerator.java @@ -12,13 +12,13 @@ import org.apache.logging.log4j.Logger; import brainwine.gameserver.GameServer; -import brainwine.gameserver.Naming; import brainwine.gameserver.StringGenerator; import brainwine.gameserver.item.Layer; import brainwine.gameserver.resource.Resource; import brainwine.gameserver.resource.ResourceFinder; import brainwine.gameserver.zone.Biome; import brainwine.gameserver.zone.Zone; +import brainwine.gameserver.zone.gen.models.TerrainType; import brainwine.gameserver.zone.gen.tasks.CaveGeneratorTask; import brainwine.gameserver.zone.gen.tasks.DecorGeneratorTask; import brainwine.gameserver.zone.gen.tasks.GeneratorTask; @@ -32,6 +32,7 @@ public class ZoneGenerator { private static final Map generators = new HashMap<>(); private static final ZoneGenerator defaultGenerator = new ZoneGenerator(); private static AsyncZoneGenerator asyncGenerator; + private final GeneratorConfig config; private final GeneratorTask terrainGenerator; private final GeneratorTask caveGenerator; private final GeneratorTask decorGenerator; @@ -42,6 +43,7 @@ public ZoneGenerator() { } public ZoneGenerator(GeneratorConfig config) { + this.config = config; terrainGenerator = new TerrainGeneratorTask(config); caveGenerator = new CaveGeneratorTask(config); decorGenerator = new DecorGeneratorTask(config); @@ -145,6 +147,10 @@ public Zone generateZone(Biome biome, int width, int height, int seed) { // Bedrock for(int x = 0; x < width; x++) { ctx.updateBlock(x, height - 1, Layer.FRONT, "ground/bedrock"); + + if(config.getTerrainType() == TerrainType.FILLED) { + ctx.updateBlock(x, 0, Layer.FRONT, "ground/bedrock"); + } } return zone; From 4f0e2aa6319921726ee9af9200220c75ffe4684e Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 5 Jan 2025 22:32:35 +0100 Subject: [PATCH 156/176] Rework loot/luck system --- .../gameserver/loot/LootManager.java | 47 +++++++++++++++---- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/loot/LootManager.java b/gameserver/src/main/java/brainwine/gameserver/loot/LootManager.java index e590efa9..bd6bafd3 100644 --- a/gameserver/src/main/java/brainwine/gameserver/loot/LootManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/loot/LootManager.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; @@ -18,15 +19,18 @@ import com.fasterxml.jackson.core.type.TypeReference; +import brainwine.gameserver.item.Item; import brainwine.gameserver.player.Player; import brainwine.gameserver.player.Skill; import brainwine.gameserver.resource.ResourceFinder; import brainwine.gameserver.util.WeightedMap; +import brainwine.gameserver.zone.Biome; import brainwine.shared.JsonHelper; public class LootManager { - public static final int[] MIN_FREQUENCIES_BY_LUCK = {18, 15, 12, 9, 7, 5, 4, 3, 2}; + public static final double LEVELS_PER_BONUS_ROLL = 6.0; + public static final int MAX_BONUS_ROLLS = 20; private static final Logger logger = LogManager.getLogger(); private final Map> lootTables = new HashMap<>(); @@ -50,20 +54,16 @@ public List getLootTable(String category) { return lootTables.getOrDefault(category, Collections.emptyList()); } - public List getEligibleLoot(Player player, String... categories) { - return getEligibleLoot(player, Arrays.asList(categories)); + public List getEligibleLoot(Biome biome, Set ignore, String... categories) { + return getEligibleLoot(biome, ignore, Arrays.asList(categories)); } - public List getEligibleLoot(Player player, Collection categories) { - int luck = player.getTotalSkillLevel(Skill.LUCK); - int minFrequency = luck > MIN_FREQUENCIES_BY_LUCK.length ? 1 : MIN_FREQUENCIES_BY_LUCK[luck - 1]; + public List getEligibleLoot(Biome biome, Set ignore, Collection categories) { List eligibleLoot = lootTables.entrySet().stream() .filter(entry -> categories.contains(entry.getKey())) .map(Entry::getValue) .flatMap(Collection::stream) - .filter(loot -> (loot.getBiome() == null || loot.getBiome() == player.getZone().getBiome()) - && (Math.random() <= luck * 0.015 || loot.getFrequency() >= minFrequency) - && !player.getInventory().getWardrobe().containsAll(loot.getItems().keySet())) + .filter(loot -> (loot.getBiome() == null || loot.getBiome() == biome) && !ignore.containsAll(loot.getItems().keySet())) .collect(Collectors.toList()); return eligibleLoot; } @@ -73,6 +73,33 @@ public Loot getRandomLoot(Player player, String... categories) { } public Loot getRandomLoot(Player player, Collection categories) { - return new WeightedMap<>(getEligibleLoot(player, categories), Loot::getFrequency).next(); + return getRandomLoot(player.getTotalSkillLevel(Skill.LUCK), player.getZone().getBiome(), player.getInventory().getWardrobe(), categories); + } + + public Loot getRandomLoot(int luck, Biome biome, Set ignore, String... categories) { + return getRandomLoot(luck, biome, ignore, Arrays.asList(categories)); + } + + public Loot getRandomLoot(int luck, Biome biome, Set ignore, Collection categories) { + WeightedMap map = new WeightedMap<>(getEligibleLoot(biome, ignore, categories), Loot::getFrequency); + Loot loot = map.next(); + double rolls = (luck - 1) / LEVELS_PER_BONUS_ROLL; + int bonusRolls = Math.min(MAX_BONUS_ROLLS, (int)rolls); + + // Turn remainder into a chance to get an extra bonus roll + if(bonusRolls < MAX_BONUS_ROLLS && Math.random() < (rolls - bonusRolls)) { + bonusRolls++; + } + + // Perform bonus rolls and return the lowest frequency loot + for(int i = 0; i < bonusRolls; i++) { + Loot next = map.next(); + + if(loot == null || (next != null && next.getFrequency() < loot.getFrequency())) { + loot = next; + } + } + + return loot; } } From 2095f05456e9804b5c20c30d67396051480fb31e Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 5 Jan 2025 22:48:59 +0100 Subject: [PATCH 157/176] Add `/loot` command --- .../gameserver/command/admin/LootCommand.java | 80 +++++++++++++++++++ .../gameserver/loot/LootManager.java | 6 +- 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/command/admin/LootCommand.java diff --git a/gameserver/src/main/java/brainwine/gameserver/command/admin/LootCommand.java b/gameserver/src/main/java/brainwine/gameserver/command/admin/LootCommand.java new file mode 100644 index 00000000..d733af95 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/command/admin/LootCommand.java @@ -0,0 +1,80 @@ +package brainwine.gameserver.command.admin; + +import static brainwine.gameserver.player.NotificationType.SYSTEM; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.command.Command; +import brainwine.gameserver.command.CommandExecutor; +import brainwine.gameserver.command.CommandInfo; +import brainwine.gameserver.loot.Loot; +import brainwine.gameserver.loot.LootManager; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.player.Skill; + +@CommandInfo(name = "loot", description = "Awards loot to a player.") +public class LootCommand extends Command { + + @Override + public void execute(CommandExecutor executor, String[] args) { + if(args.length < 2) { + executor.notify(String.format("Usage: %s", getUsage(executor)), SYSTEM); + return; + } + + Player target = GameServer.getInstance().getPlayerManager().getPlayer(args[0]); + + // Check if player exists + if(target == null) { + executor.notify("That player does not exist.", SYSTEM); + return; + } + + // Check if player is online + if(!target.isOnline()) { + executor.notify(String.format("%s is not online.", target.getName()), SYSTEM); + return; + } + + int luck = target.getTotalSkillLevel(Skill.LUCK); + int maxLuck = (int)(LootManager.MAX_BONUS_ROLLS * LootManager.LEVELS_PER_BONUS_ROLL); + + if(args.length >= 3) { + try { + luck = Math.max(1, Math.min(maxLuck, Integer.parseInt(args[2]))); + } catch(NumberFormatException e) { + executor.notify("Luck must be a valid number.", SYSTEM); + return; + } + } + + String category = args[1]; + LootManager lootManager = GameServer.getInstance().getLootManager(); + + // Check if loot table exists + if(lootManager.getLootTable(category) == null) { + executor.notify(String.format("Loot category must be one of: %s", lootManager.getLootCategories()), SYSTEM); + return; + } + + Loot loot = lootManager.getRandomLoot(luck, target.getZone().getBiome(), target.getInventory().getWardrobe(), category); + + // Check if eligible loot was found + if(loot == null) { + executor.notify(String.format("Could not find any eligible loot for category '%s'.", category), SYSTEM); + return; + } + + target.awardLoot(loot); + executor.notify(String.format("Awarded level %s %s loot to %s.", luck, category, target.getName()), SYSTEM); + } + + @Override + public String getUsage(CommandExecutor executor) { + return "/loot [luck]"; + } + + @Override + public boolean canExecute(CommandExecutor executor) { + return executor.isAdmin(); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/loot/LootManager.java b/gameserver/src/main/java/brainwine/gameserver/loot/LootManager.java index bd6bafd3..3e648a15 100644 --- a/gameserver/src/main/java/brainwine/gameserver/loot/LootManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/loot/LootManager.java @@ -51,7 +51,11 @@ private void loadLootTables() { } public List getLootTable(String category) { - return lootTables.getOrDefault(category, Collections.emptyList()); + return lootTables.get(category); + } + + public Set getLootCategories() { + return Collections.unmodifiableSet(lootTables.keySet()); } public List getEligibleLoot(Biome biome, Set ignore, String... categories) { From a000e924de28d355ad880b10f65d69c7f4d500b6 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Mon, 6 Jan 2025 00:48:06 +0100 Subject: [PATCH 158/176] Fix compile error --- .../main/java/brainwine/gameserver/player/Inventory.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java index 4a21b6c0..df43410b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java @@ -3,9 +3,11 @@ import java.beans.ConstructorProperties; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonIncludeProperties; @@ -172,8 +174,8 @@ public ItemContainer getAccessories() { return accessories; } - public List getWardrobe() { - return items.keySet().stream().filter(item -> item.isClothing() && hasItem(item)).collect(Collectors.toList()); + public Set getWardrobe() { + return items.keySet().stream().filter(item -> item.isClothing() && hasItem(item)).collect(Collectors.toCollection(HashSet::new)); } @JsonValue From 64a54d33003d5f2546aeb1cd52777a44daec591f Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 7 Jan 2025 22:08:42 +0100 Subject: [PATCH 159/176] Implement regen bonus --- .../brainwine/gameserver/GameConfiguration.java | 16 ++++++++++++---- .../java/brainwine/gameserver/item/Item.java | 7 +++++++ .../brainwine/gameserver/player/Inventory.java | 11 +++++++++++ .../java/brainwine/gameserver/player/Player.java | 16 ++-------------- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java b/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java index 2e33c8a0..a06e55e9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java +++ b/gameserver/src/main/java/brainwine/gameserver/GameConfiguration.java @@ -134,15 +134,23 @@ private static void configure() { } } - // Map skill bonuses + // Map stat bonuses Map bonuses = MapHelper.getMap(config, "bonus"); if(bonuses != null) { Map skillBonuses = new HashMap<>(); - bonuses.forEach((type, amount) -> { - if(amount instanceof Integer && Skill.fromId(type) != null) { - skillBonuses.put(type, (int)amount); + bonuses.forEach((type, value) -> { + if(!(value instanceof Number)) { + return; + } + + Number amount = (Number)value; + + if(Skill.fromId(type) != null) { + skillBonuses.put(type, amount.intValue()); + } else if("regen".equals(type)) { + config.put("regen_bonus", amount.doubleValue()); } }); diff --git a/gameserver/src/main/java/brainwine/gameserver/item/Item.java b/gameserver/src/main/java/brainwine/gameserver/item/Item.java index 9d67eb35..d820fe53 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/Item.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/Item.java @@ -159,6 +159,9 @@ public class Item { @JsonProperty("loot") private String[] lootCategories = {}; + @JsonProperty("regen_bonus") + private double regenBonus = 1.0; + @JsonProperty("tool_bonus") private double toolBonus; @@ -528,6 +531,10 @@ public String[] getLootCategories() { return lootCategories; } + public double getRegenBonus() { + return regenBonus; + } + public double getToolBonus() { return toolBonus; } diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java index df43410b..c2a96c9c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java @@ -9,6 +9,7 @@ import java.util.Map.Entry; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import com.fasterxml.jackson.annotation.JsonIncludeProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -174,6 +175,16 @@ public ItemContainer getAccessories() { return accessories; } + // TODO hidden accessories + public int getSkillBonus(Skill skill) { + return Stream.of(accessories.getItems()).map(item -> item.getSkillBonus(skill)).max(Integer::compareTo).orElse(0); + } + + // TODO hidden accessories + public double getRegenBonus() { + return Stream.of(accessories.getItems()).map(Item::getRegenBonus).min(Double::compareTo).orElse(1.0); + } + public Set getWardrobe() { return items.keySet().stream().filter(item -> item.isClothing() && hasItem(item)).collect(Collectors.toCollection(HashSet::new)); } diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index 2b84571d..eee256bd 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -231,7 +231,7 @@ public void tick(float deltaTime) { } // Regenerate health out of combat - if(!isDead() && now >= Math.max(lastHealthRegenAt, lastDamagedAt) + BASE_REGEN_INTERVAL) { + if(!isDead() && health < getMaxHealth() && now >= Math.max(lastHealthRegenAt, lastDamagedAt) + BASE_REGEN_INTERVAL * inventory.getRegenBonus()) { heal(BASE_REGEN_AMOUNT); lastHealthRegenAt = now; } @@ -1433,19 +1433,7 @@ public void setSkillLevel(Skill skill, int level) { } public int getTotalSkillLevel(Skill skill) { - int accessorySkillLevel = 0; - - // Get the highest skill bonus accessory - for(Item accessory : inventory.getAccessories().getItems()) { - int skillBonus = accessory.getSkillBonuses().getOrDefault(skill, 0); - - if(skillBonus > accessorySkillLevel) { - accessorySkillLevel = skillBonus; - } - } - - // TODO account for exoskeleton bonuses - return getSkillLevel(skill) + accessorySkillLevel; + return getSkillLevel(skill) + inventory.getSkillBonus(skill); } public float getNormalizedSkill(Skill skill) { From 9c5ba6ee0fb90c7ad89b8b770f729d2f7c213e75 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 7 Jan 2025 22:12:30 +0100 Subject: [PATCH 160/176] Oops --- gameserver/src/main/java/brainwine/gameserver/item/Item.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/Item.java b/gameserver/src/main/java/brainwine/gameserver/item/Item.java index d820fe53..a60c5d85 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/Item.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/Item.java @@ -471,6 +471,10 @@ public boolean requiresMembership() { return membership; } + public int getSkillBonus(Skill skill) { + return skillBonuses.getOrDefault(skill, 0); + } + public Map getSkillBonuses() { return skillBonuses; } From 6fd0cd986266123819dbea34523b3928925a681a Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Wed, 8 Jan 2025 03:18:35 +0100 Subject: [PATCH 161/176] Spawn effect on zone enter --- .../src/main/java/brainwine/gameserver/player/Player.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index eee256bd..e13b8bcf 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -464,7 +464,6 @@ public void onZoneEntered() { if(!customSpawn || !zone.areCoordinatesInBounds(blockX, blockY)) { x = spawnX; y = spawnY; - customSpawn = true; // Remember position until zone changes } // Set skills for new players @@ -537,6 +536,11 @@ public void onZoneEntered() { notify("Welcome to " + zone.getName(), NotificationType.WELCOME); } + if(!customSpawn) { + zone.spawnEffect(x + 0.5F, y - 0.75F, "spawn", 20); + customSpawn = true; // Remember position until zone changes + } + // Send social info sendMessage(new FollowMessage(followees.stream().map(playerManager::getPlayerById).filter(Objects::nonNull).collect(Collectors.toList()), 0)); sendMessage(new FollowMessage(followers.stream().map(playerManager::getPlayerById).filter(Objects::nonNull).collect(Collectors.toList()), 1)); @@ -750,7 +754,7 @@ public void respawn() { sendMessage(new PlayerPositionMessage(spawnX, spawnY)); sendMessageToPeers(new EntityStatusMessage(this, EntityStatus.REVIVED)); - zone.spawnEffect(spawnX, spawnY, "spawn", 20); + zone.spawnEffect(spawnX + 0.5F, spawnY - 0.75F, "spawn", 20); } /** From e2aa4908324c0937c9b436c69531683c7bcbb427 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Wed, 8 Jan 2025 04:53:50 +0100 Subject: [PATCH 162/176] Send banned players to hell --- .../brainwine/gameserver/player/Player.java | 2 +- .../server/requests/AuthenticateRequest.java | 37 ++++++++++++------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index e13b8bcf..d62c12dd 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -1195,7 +1195,7 @@ public void ban(String reason, OffsetDateTime until) { public void ban(Player issuer, String reason, OffsetDateTime endDate) { bans.add(new PlayerRestriction(issuer, reason, endDate)); - kick(String.format("You have been banned: %s", reason)); + kick(String.format("You have been banned: %s", reason), true); } public void unban() { diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/AuthenticateRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/AuthenticateRequest.java index d1db5bb2..f391b0a6 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/AuthenticateRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/AuthenticateRequest.java @@ -1,14 +1,17 @@ package brainwine.gameserver.server.requests; import java.time.format.DateTimeFormatter; +import java.util.Locale; import brainwine.gameserver.GameServer; +import brainwine.gameserver.player.NotificationType; import brainwine.gameserver.player.Player; import brainwine.gameserver.player.PlayerManager; import brainwine.gameserver.player.PlayerRestriction; import brainwine.gameserver.server.OptionalField; import brainwine.gameserver.server.Request; import brainwine.gameserver.server.RequestInfo; +import brainwine.gameserver.server.messages.NotificationMessage; import brainwine.gameserver.server.pipeline.Connection; import brainwine.gameserver.zone.Zone; @@ -44,22 +47,28 @@ public void process(Connection connection) { PlayerRestriction ban = player.getCurrentBan(); Zone zone = player.getZone(); - // Deny access if the player is currently banned if(ban != null) { - connection.kick(String.format("You are banned from the server until %s for: %s", - ban.getEndDate().format(DateTimeFormatter.RFC_1123_DATE_TIME), ban.getReason())); - return; - } - - // Try to put player in a random zone if current zone is null or cannot be joined - if(zone == null || !zone.canJoin(player)) { + // Send player to jail world if they're banned + zone = server.getZoneManager().getZoneByName("Hell"); + String banMessage = String.format("You are banned from the server until\n%s for: %s", + ban.getEndDate().format(DateTimeFormatter.ofPattern("d MMMM uuuu HH:mm:ss", Locale.ENGLISH)), ban.getReason()); + + // Kick player with ban message if no jail world exists + if(zone == null) { + connection.kick(banMessage); + return; + } + + connection.sendDelayedMessage(new NotificationMessage(banMessage, NotificationType.MAINTENANCE), 5000); // Slightly hacky but shouldn't cause any issues + } else if(zone == null || !zone.canJoin(player)) { + // Try to put player in a random zone if current zone is null or cannot be joined zone = server.getZoneManager().findBeginnerZone(); - } - - // Kick player if zone is still null (aka it failed to find a suitable random zone) - if(zone == null) { - connection.kick("No default zone could be found."); - return; + + // Kick player if zone is still null (aka it failed to find a suitable random zone) + if(zone == null) { + connection.kick("Sorry, we couldn't find a world for you to join."); + return; + } } player.setConnection(connection); From 9552c50ac0e991711a9483e9a258816c01bc84cf Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Wed, 8 Jan 2025 05:13:59 +0100 Subject: [PATCH 163/176] Fix crash --- .../brainwine/gameserver/server/requests/DialogRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java index 924fa3f7..49dfc1c3 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/DialogRequest.java @@ -71,7 +71,7 @@ private void showPlayerDialog(Player player) { dialog.addSection(new DialogSection().setText(String.format("Goto %s", subject.getZone().getName())).setChoice("visit")); } } else { - dialog.addSection(new DialogSection().setText(String.format("Offline", subject.getZone().getName()))); + dialog.addSection(new DialogSection().setText("Offline")); } // Follow section From fce8e703ddb46c5ba49cf3e1533843d5b3254c68 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 9 Jan 2025 00:14:57 +0100 Subject: [PATCH 164/176] Only send delayed messages if channel is still open --- .../java/brainwine/gameserver/server/pipeline/Connection.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/pipeline/Connection.java b/gameserver/src/main/java/brainwine/gameserver/server/pipeline/Connection.java index 6315dcc7..9db78094 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/pipeline/Connection.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/pipeline/Connection.java @@ -80,7 +80,9 @@ public ChannelFuture sendMessage(Message message) { public void sendDelayedMessage(Message message, int delay) { channel.eventLoop().schedule(() -> { - sendMessage(message); + if(channel.isOpen()) { + sendMessage(message); + } }, delay, TimeUnit.MILLISECONDS); } From 59b0637ad0a676ed5bbf54caf56121c2fd63da07 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 9 Jan 2025 01:21:33 +0100 Subject: [PATCH 165/176] Rework horrid player spawning code... again --- .../brainwine/gameserver/player/Player.java | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index d62c12dd..d8258263 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -138,8 +138,10 @@ public class Player extends Entity implements CommandExecutor { private double breath = 1.0; private double thirst; private double cold; - private int spawnX = -1; - private int spawnY = -1; + private int spawnX; + private int spawnY; + private int enterX; + private int enterY; private int teleportX; private int teleportY; private boolean stealth; @@ -154,6 +156,7 @@ public class Player extends Entity implements CommandExecutor { private long lastTrackedEntityUpdate; private long lastLandmarkVoteAt; private long lastHealthRegenAt; + private Zone previousZone; private Zone nextZone; private Connection connection; @@ -447,8 +450,10 @@ public Map getStatusConfig() { * Called by {@link Zone#addEntity(Entity)} when the player is added to it. */ public void onZoneEntered() { - // Find a random new spawn if one isn't assigned yet - if(spawnX == -1 || spawnY == -1) { + boolean spawnEffect = false; + + // Find new spawn point if zone has changed + if(zone != previousZone) { MetaBlock spawn = zone.getRandomSpawnBlock(); if(spawn == null) { @@ -458,14 +463,24 @@ public void onZoneEntered() { spawnX = spawn.getX() + 1; spawnY = spawn.getY(); } + + x = spawnX; + y = spawnY; + spawnEffect = true; } - // Set the player's location to their spawn location if no custom spawn is set or if they are out of bounds - if(!customSpawn || !zone.areCoordinatesInBounds(blockX, blockY)) { + // Handle custom spawn location + if(zone != nextZone) { x = spawnX; y = spawnY; + spawnEffect = true; + } else if(customSpawn && zone == nextZone) { + x = enterX; + y = enterY; } + customSpawn = false; + // Set skills for new players for(Skill skill : Skill.values()) { if(!skills.containsKey(skill)) { @@ -536,9 +551,8 @@ public void onZoneEntered() { notify("Welcome to " + zone.getName(), NotificationType.WELCOME); } - if(!customSpawn) { + if(spawnEffect) { zone.spawnEffect(x + 0.5F, y - 0.75F, "spawn", 20); - customSpawn = true; // Remember position until zone changes } // Send social info @@ -569,6 +583,7 @@ public void onDisconnect() { lastHeartbeat = 0; lastPlacement = null; clientVersion = null; + previousZone = zone; if(zone != null) { zone.removeEntity(this); @@ -577,8 +592,9 @@ public void onDisconnect() { // Are we switching zones? Then set the new zone. if(changingZones) { zone = nextZone; - nextZone = null; changingZones = false; + } else { + nextZone = zone; } // Cancel existing trade session @@ -646,12 +662,10 @@ public void changeZone(Zone zone) { public void changeZone(Zone zone, int x, int y) { changingZones = true; - nextZone = zone; - spawnX = -1; - spawnY = -1; customSpawn = x != -1 && y != -1; - this.x = x; - this.y = y; + nextZone = zone; + enterX = x; + enterY = y; sendMessage(new EventMessage("playerWillChangeZone", null)); kick("Teleporting...", true); } From 5929054fab371f4c9b0bfffc50179fbf5e1e3f9f Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 9 Jan 2025 01:29:16 +0100 Subject: [PATCH 166/176] Rescue out-of-bounds players --- .../src/main/java/brainwine/gameserver/player/Player.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index d8258263..2f481e27 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -481,6 +481,14 @@ public void onZoneEntered() { customSpawn = false; + // Rescue player if they're out of bounds somehow + // blockX and blockY might not be assigned yet so we check the absolute position + if(!zone.areCoordinatesInBounds((int)x, (int)y)) { + x = spawnX; + y = spawnY; + spawnEffect = true; + } + // Set skills for new players for(Skill skill : Skill.values()) { if(!skills.containsKey(skill)) { From ea2e753031c6e5a81e954888671e0ede9ebb4be9 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Thu, 9 Jan 2025 19:06:21 +0100 Subject: [PATCH 167/176] Add `stack` mod type --- .../src/main/java/brainwine/gameserver/item/ModType.java | 1 + .../gameserver/server/requests/BlockMineRequest.java | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/ModType.java b/gameserver/src/main/java/brainwine/gameserver/item/ModType.java index 18069ac6..c8491007 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/ModType.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/ModType.java @@ -6,6 +6,7 @@ public enum ModType { DECAY, ROTATION, + STACK, @JsonEnumDefaultValue NONE; diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index c9bf8c6d..a073113c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -202,6 +202,11 @@ public void process(Player player) { player.getStatistics().trackItemScavenged(item); } + // Check stack mod + if(item.getMod() == ModType.STACK) { + quantity = Math.max(1, block.getMod(layer)); + } + zone.updateBlock(x, y, layer, 0, 0, player); // Apply mining bonus if there is one From 0dc60162ab89d481c52b5f61834805f01842c9b3 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 12 Jan 2025 03:24:02 +0100 Subject: [PATCH 168/176] Rework shutdown code a bit --- .../java/brainwine/gameserver/GameServer.java | 16 ++++++++--- .../brainwine/gameserver/server/Server.java | 2 +- .../gameserver/zone/ChunkManager.java | 28 +++++++++++-------- .../zone/gen/AsyncZoneGenerator.java | 9 ++---- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/GameServer.java b/gameserver/src/main/java/brainwine/gameserver/GameServer.java index e29fdf38..8b737a81 100644 --- a/gameserver/src/main/java/brainwine/gameserver/GameServer.java +++ b/gameserver/src/main/java/brainwine/gameserver/GameServer.java @@ -80,10 +80,7 @@ public void tick() { long now = System.currentTimeMillis(); float deltaTime = (now - lastTick) / 1000.0F; // in seconds lastTick = now; - - while(!tasks.isEmpty()) { - tasks.poll().run(); - } + pollTasks(); if(lastSave + GLOBAL_SAVE_INTERVAL < System.currentTimeMillis()) { zoneManager.saveZones(); @@ -108,13 +105,24 @@ public void queueSynchronousTask(Runnable task) { } } + /** + * Polls and executes all currently queued tasks. + */ + private void pollTasks() { + while(!tasks.isEmpty()) { + tasks.poll().run(); + } + } + /** * Called by the bootstrapper when the program closes. */ public void onShutdown() { logger.info(SERVER_MARKER, "Shutting down GameServer ..."); + playerManager.getOnlinePlayers().forEach(player -> player.kick("Server is shutting down.")); server.close(); ZoneGenerator.stopAsyncZoneGenerator(true); + pollTasks(); // Run any remaining tasks to ensure everything is cleaned up properly logger.info(SERVER_MARKER, "Saving zone data ..."); zoneManager.onShutdown(); logger.info(SERVER_MARKER, "Saving player data ..."); diff --git a/gameserver/src/main/java/brainwine/gameserver/server/Server.java b/gameserver/src/main/java/brainwine/gameserver/server/Server.java index 3fb4a3f4..d6469f56 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/Server.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/Server.java @@ -96,6 +96,6 @@ protected void initChannel(Channel channel) throws Exception { public void close() { logger.info(SERVER_MARKER, "Closing endpoints ..."); - eventLoopGroup.shutdownGracefully(); + eventLoopGroup.shutdownGracefully().awaitUninterruptibly(); } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/ChunkManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/ChunkManager.java index 27914494..ff0fa07d 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/ChunkManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/ChunkManager.java @@ -147,24 +147,16 @@ public void saveChunks() { List inactiveChunks = new ArrayList<>(); for(Chunk chunk : chunks.values()) { - saveChunk(chunk); - boolean active = false; - - for(Player player : zone.getPlayers()) { - if(player.isChunkActive(chunk)) { - active = true; - break; - } - } - - if(!active) { + if(!isChunkActive(chunk)) { inactiveChunks.add(chunk); + zone.onChunkUnloaded(chunk); // Perform cleanup *before* the chunk is saved and unindexed in case any last-minute changes need to be made } + + saveChunk(chunk); } for(Chunk chunk : inactiveChunks) { chunks.remove(getChunkIndex(chunk.getX(), chunk.getY())); - zone.onChunkUnloaded(chunk); } } @@ -247,6 +239,18 @@ public void putChunk(int index, Chunk chunk) { } } + public boolean isChunkActive(int x, int y) { + return zone.areCoordinatesInBounds(x, y) && isChunkActive(getChunkIndex(x, y)); + } + + public boolean isChunkActive(int index) { + return zone.getPlayers().stream().anyMatch(player -> player.isChunkActive(index)); + } + + public boolean isChunkActive(Chunk chunk) { + return zone.getPlayers().stream().anyMatch(player -> player.isChunkActive(chunk)); + } + public boolean isChunkLoaded(int x, int y) { return zone.areCoordinatesInBounds(x, y) && isChunkLoaded(getChunkIndex(x, y)); } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/gen/AsyncZoneGenerator.java b/gameserver/src/main/java/brainwine/gameserver/zone/gen/AsyncZoneGenerator.java index fdd637c3..364c0818 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/gen/AsyncZoneGenerator.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/gen/AsyncZoneGenerator.java @@ -49,14 +49,11 @@ public void run() { if(callback != null) { GameServer gameServer = GameServer.getInstance(); + gameServer.queueSynchronousTask(() -> callback.accept(generated)); + // Shouldn't be an issue, but log a warning anyway. if(gameServer.shouldStop()) { - logger.warn(SERVER_MARKER, "Server shutdown has been requested while generating a zone!" - + " Callback will be fired immediately on the async zone generator thread." - + " Don't blame me for what happens!"); - callback.accept(generated); - } else { - gameServer.queueSynchronousTask(() -> callback.accept(generated)); + logger.warn(SERVER_MARKER, "Server shutdown has been requested while generating a zone!"); } } } From ab3d3b42d438a928ba3c5251da6f443c67736621 Mon Sep 17 00:00:00 2001 From: RueduBac Date: Mon, 20 Jan 2025 00:22:59 +0300 Subject: [PATCH 169/176] Interpolate switch/touchplate messages (#76) --- .../gameserver/entity/EntityConfig.java | 7 +++- .../item/interactions/SwitchInteraction.java | 32 +++++++++++++------ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java b/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java index fdb43f88..ba6c7fb4 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/EntityConfig.java @@ -26,6 +26,7 @@ public class EntityConfig { private final String name; private final int type; + private String title = "Unknown"; private int experienceYield; private float maxHealth = Entity.DEFAULT_HEALTH; private float baseSpeed = 3; @@ -68,7 +69,11 @@ public String getName() { public int getType() { return type; } - + + public String getTitle() { + return title; + } + @JsonProperty("xp") public int getExperienceYield() { return experienceYield; diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java index e3d83cda..a2eb16fd 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/SwitchInteraction.java @@ -40,7 +40,7 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i } // Show configured message to nearby players - String message = metaBlock.getStringProperty("m"); + String message = interpolateMessage(entity, metaBlock.getStringProperty("m")); if(message != null && !message.isEmpty()) { float effectX = x + item.getBlockWidth() / 2.0F; @@ -165,7 +165,7 @@ private void switchExploder(Zone zone, Entity entity, MetaBlock metaBlock) { } private void switchSign(Zone zone, Entity entity, MetaBlock metaBlock, MetaBlock switchMeta) { - String message = switchMeta.hasProperty("m") ? switchMeta.getStringProperty("m").trim() : ""; + String message = interpolateMessage(entity, switchMeta.hasProperty("m") ? switchMeta.getStringProperty("m").trim() : ""); boolean lock = metaBlock.hasProperty("lock") && metaBlock.getStringProperty("lock").equalsIgnoreCase("yes"); Item item = metaBlock.getItem(); @@ -184,13 +184,7 @@ private void switchSign(Zone zone, Entity entity, MetaBlock metaBlock, MetaBlock } } - // Update sign text - String name = entity.getName(); - - if(name != null) { - message = message.replaceAll("%t%", name); - } - + // Update sign text String separator = "\n"; String[] keys = {"t1", "t2", "t3", "t4"}; String[] segments = WordUtils.wrap(message, 20, separator, true).split(separator, 4); @@ -213,4 +207,24 @@ private void switchSign(Zone zone, Entity entity, MetaBlock metaBlock, MetaBlock zone.spawnEffect(effectX, effectY, "area steam", 10); zone.sendBlockMetaUpdate(metaBlock); } + + private String interpolateMessage(Entity entity, String message) { + if(message == null) return null; + + if(entity != null) { + String name; + if(entity.isPlayer()) { + name = entity.getName(); + } else { + Npc npc = (Npc) entity; + name = npc.getName() == null ? npc.getConfig().getTitle() : npc.getName(); + } + + if(name != null) { + message = message.replaceAll("\\*(player|mob)\\*", name); + } + } + + return message; + } } From e9b34759676472ec7c74e94f039f930a26815f0b Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Sun, 19 Jan 2025 00:41:16 +0100 Subject: [PATCH 170/176] Force player out of hell if they're unbanned --- .../src/main/java/brainwine/gameserver/player/Player.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index 2f481e27..00c2a9e8 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -1230,6 +1230,10 @@ public void unban(Player issuer) { if(currentBan != null) { currentBan.pardon(issuer); } + + if(isOnline()) { + changeZone(null); + } } public boolean isBanned() { From 6ded6722e06a0e3e1733537c4c4431e6114cc769 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Mon, 20 Jan 2025 05:25:06 +0100 Subject: [PATCH 171/176] Prevent dungeon switches from being blown up (#73) --- .../server/requests/BlockMineRequest.java | 20 ++++--------- .../java/brainwine/gameserver/zone/Zone.java | 28 +++++++++++++++++-- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java index a073113c..2bf9f5d5 100644 --- a/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java +++ b/gameserver/src/main/java/brainwine/gameserver/server/requests/BlockMineRequest.java @@ -1,7 +1,5 @@ package brainwine.gameserver.server.requests; -import java.util.Collections; -import java.util.List; import java.util.Map; import brainwine.gameserver.entity.Entity; @@ -132,19 +130,11 @@ public void process(Player player) { // Check if block is a natural switch with an active door if(!player.isGodMode() && !metaBlock.hasOwner() && item.hasUse(ItemUseType.SWITCH)) { - List> positions = MapHelper.getList(metadata, ">", Collections.emptyList()); - - for(List position : positions) { - Block target = zone.getBlock(position.get(0), position.get(1)); - - if(target != null) { - Item switchedItem = target.getFrontItem(); - - if(switchedItem.hasUse(ItemUseType.SWITCHED)) { - fail(player, String.format("This switch cannot be mined before its %s.", switchedItem.getTitle().toLowerCase())); - return; - } - } + Item switchedItem = zone.getSwitchedItem(metaBlock); + + if(!switchedItem.isAir()) { + fail(player, String.format("This switch cannot be mined before its %s.", switchedItem.getTitle().toLowerCase())); + return; } } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index cad7eae2..ce975aad 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -11,6 +11,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.UUID; @@ -444,7 +445,7 @@ public void explode(int x, int y, float radius, Entity cause, boolean destructiv if(processed.contains(index)) { continue; } - + // Skip if not in bounds if(!areCoordinatesInBounds(positionX, positionY)) { break; @@ -469,6 +470,9 @@ public void explode(int x, int y, float radius, Entity cause, boolean destructiv break; } + // Only count as processed if the ray can no longer be stopped + processed.add(index); + // Metadata check MetaBlock metaBlock = getMetaBlock(positionX, positionY); @@ -478,11 +482,13 @@ public void explode(int x, int y, float radius, Entity cause, boolean destructiv continue; } - // TODO dungeon switch check + // Do not destroy block if it is a natural dungeon switch with an active linked item + if(!metaBlock.hasOwner() && !getSwitchedItem(metaBlock).isAir()) { + continue; + } } affectedBlocks.add(position); - processed.add(index); } } @@ -544,6 +550,22 @@ public void explodeLiquid(int x, int y, int range, Item liquid) { } } + public Item getSwitchedItem(MetaBlock metaBlock) { + // Do nothing if meta block is not a switch + if(!metaBlock.getItem().hasUse(ItemUseType.SWITCH)) { + return Item.AIR; + } + + // TODO this implementation assumes that all switched items have metadata + List> positions = MapHelper.getList(metaBlock.getMetadata(), ">", Collections.emptyList()); + return positions.stream() + .map(position -> getMetaBlock(position.get(0), position.get(1))) // Map to meta block + .filter(Objects::nonNull) // Remove null meta blocks + .map(MetaBlock::getItem) // Map to item + .filter(item -> item.hasUse(ItemUseType.SWITCHED)) // Remove non-switched items + .findFirst().orElse(Item.AIR); // Return first entry or air if none exist + } + public boolean isBlockWhole(int x, int y) { return areCoordinatesInBounds(x, y) && getBlock(x, y).getFrontItem().isWhole(); } From bc50ed8268631d02a0c9073da82aef5e096d67bc Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 22 Apr 2025 16:44:26 +0200 Subject: [PATCH 172/176] Minigame base + Pandora --- deepworld-config | 2 +- .../java/brainwine/gameserver/GameServer.java | 2 + .../brainwine/gameserver/entity/Entity.java | 26 +- .../brainwine/gameserver/entity/npc/Npc.java | 13 + .../gameserver/item/ItemUseType.java | 2 + .../interactions/MinigameInteraction.java | 94 ++++++ .../gameserver/minigame/Minigame.java | 206 ++++++++++++ .../gameserver/minigame/Pandora.java | 316 ++++++++++++++++++ .../gameserver/minigame/Participant.java | 49 +++ .../brainwine/gameserver/player/Player.java | 42 ++- .../gameserver/zone/EntityManager.java | 8 +- .../java/brainwine/gameserver/zone/Zone.java | 79 ++++- gameserver/src/main/resources/pandora.json | 146 ++++++++ 13 files changed, 966 insertions(+), 19 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/item/interactions/MinigameInteraction.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/minigame/Minigame.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/minigame/Pandora.java create mode 100644 gameserver/src/main/java/brainwine/gameserver/minigame/Participant.java create mode 100644 gameserver/src/main/resources/pandora.json diff --git a/deepworld-config b/deepworld-config index 29ad0a9b..34e1ec6f 160000 --- a/deepworld-config +++ b/deepworld-config @@ -1 +1 @@ -Subproject commit 29ad0a9b55097fc86be128ea49bdec8d26a28184 +Subproject commit 34e1ec6fc0ecb3633b45b8868ae7c8fc3477696c diff --git a/gameserver/src/main/java/brainwine/gameserver/GameServer.java b/gameserver/src/main/java/brainwine/gameserver/GameServer.java index 8b737a81..f31c8d94 100644 --- a/gameserver/src/main/java/brainwine/gameserver/GameServer.java +++ b/gameserver/src/main/java/brainwine/gameserver/GameServer.java @@ -13,6 +13,7 @@ import brainwine.gameserver.command.CommandManager; import brainwine.gameserver.entity.EntityRegistry; import brainwine.gameserver.loot.LootManager; +import brainwine.gameserver.minigame.Pandora; import brainwine.gameserver.player.NotificationType; import brainwine.gameserver.player.PlayerManager; import brainwine.gameserver.prefab.PrefabManager; @@ -50,6 +51,7 @@ public GameServer() { EntityRegistry.init(); EntityManager.loadEntitySpawns(); GrowthManager.loadGrowthData(); + Pandora.loadConfig(); lootManager = new LootManager(); prefabManager = new PrefabManager(); ZoneGenerator.init(); diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java index 8bb3e79c..a4775d86 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/Entity.java @@ -10,6 +10,7 @@ import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.item.Layer; +import brainwine.gameserver.minigame.Minigame; import brainwine.gameserver.player.Player; import brainwine.gameserver.server.Message; import brainwine.gameserver.server.messages.EffectMessage; @@ -51,6 +52,7 @@ public abstract class Entity { protected FacingDirection direction = FacingDirection.WEST; protected int animation; protected boolean invulnerable; + protected Minigame minigame; protected EntityAttack lastAttack; // Used for tracking in entity deaths -- do not use this for anything else! protected long lastDamagedAt; @@ -72,6 +74,10 @@ public void die(EntityAttack cause) { // Override } + public void attacked(EntityAttack attack, float damage) { + // Override + } + public void heal(float amount) { if(health > 0) { setHealth(health + amount); @@ -269,8 +275,12 @@ public boolean isDead() { } public void setHealth(float health) { - float maxHealth = getMaxHealth(); - this.health = health < 0 ? 0 : health > maxHealth ? maxHealth : health; + float damage = this.health - Math.max(0.0F, health); + this.health = Math.max(0.0F, Math.min(getMaxHealth(), health)); + + if(lastAttack != null) { + attacked(lastAttack, damage); + } if(this.health <= 0.0F) { die(lastAttack); @@ -367,6 +377,18 @@ public boolean isInvulnerable() { return invulnerable; } + public void setMinigame(Minigame minigame) { + this.minigame = minigame; + } + + public boolean hasActiveMinigame() { + return minigame != null && minigame.isActive(); + } + + public Minigame getMinigame() { + return minigame; + } + public void setZone(Zone zone) { this.zone = zone; } diff --git a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java index 4d5b0639..e60ed8c0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java +++ b/gameserver/src/main/java/brainwine/gameserver/entity/npc/Npc.java @@ -20,6 +20,7 @@ import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.Layer; +import brainwine.gameserver.minigame.Minigame; import brainwine.gameserver.player.Appearance; import brainwine.gameserver.player.Player; import brainwine.gameserver.util.MapHelper; @@ -208,6 +209,18 @@ public void die(EntityAttack cause) { } } } + + // Minigame tracking + if(hasActiveMinigame()) { + minigame.entityKilled(this, cause); + } + } + + @Override + public void attacked(EntityAttack attack, float damage) { + if(hasActiveMinigame()) { + minigame.entityAttacked(this, attack, damage); + } } @Override diff --git a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java index 9f13ec28..2bbe7f76 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/ItemUseType.java @@ -12,6 +12,7 @@ import brainwine.gameserver.item.interactions.GeckInteraction; import brainwine.gameserver.item.interactions.ItemInteraction; import brainwine.gameserver.item.interactions.LandmarkInteraction; +import brainwine.gameserver.item.interactions.MinigameInteraction; import brainwine.gameserver.item.interactions.NoteInteraction; import brainwine.gameserver.item.interactions.RecyclerInteraction; import brainwine.gameserver.item.interactions.SpawnInteraction; @@ -42,6 +43,7 @@ public enum ItemUseType { FIELDABLE, FLY, LANDMARK(new LandmarkInteraction()), + MINIGAME(new MinigameInteraction()), MOVE, MULTI, NOTE(new NoteInteraction()), diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/MinigameInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/MinigameInteraction.java new file mode 100644 index 00000000..6691fcd2 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/MinigameInteraction.java @@ -0,0 +1,94 @@ +package brainwine.gameserver.item.interactions; + +import java.util.Map; + +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.item.Item; +import brainwine.gameserver.item.Layer; +import brainwine.gameserver.minigame.Minigame; +import brainwine.gameserver.minigame.Pandora; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.util.MapHelper; +import brainwine.gameserver.zone.MetaBlock; +import brainwine.gameserver.zone.Zone; + +@SuppressWarnings("unchecked") +public class MinigameInteraction implements ItemInteraction { + + @Override + public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item item, int mod, MetaBlock metaBlock, + Object config, Object[] data) { + // Do nothing if entity is not a player + if(!entity.isPlayer()) { + return; + } + + // Check item (to prevent dialog spoof) + if(!zone.isChunkLoaded(x, y) || zone.getBlock(x, y).getFrontItem() != item) { + return; + } + + // Check & interact with active minigame + // TODO verifying the type wouldn't be a bad idea + Minigame activeMinigame = zone.getMinigame(x, y); + Player player = (Player)entity; + + if(activeMinigame != null) { + activeMinigame.onInteract(player); + return; + } + + // Do nothing if config is invalid + if(!(config instanceof Map)) { + return; + } + + Map configMap = (Map)config; + String type = MapHelper.getString(configMap, "type"); + + // Check if type is present + if(type == null) { + player.notify("Minigame type has not been configured."); + return; + } + + // Handle custom minigame + if(type.equals("custom")) { + player.notify("Sorry, custom minigames are not supported yet."); + return; + } + + Map startDialog = MapHelper.getMap(configMap, "start_dialog"); + + // Show start dialog if present and input hasn't been supplied + if(startDialog != null && data == null) { + player.showDialog(startDialog, input -> interact(zone, entity, x, y, layer, item, mod, metaBlock, config, input)); + return; + } else if(startDialog != null && data.length == 1 && "cancel".equals(data[0])) { + return; // Cancel action + } + + // Check if max number of minigames has been reached + if(zone.getMinigameCount() >= Zone.MAX_CONCURRENT_MINIGAMES) { + player.notify("Sorry, the maximum number of active minigames has been reached."); + return; + } + + Minigame minigame = null; + + // Create minigame session based on type + // TODO enum? + switch(type) { + case "pandora": + minigame = new Pandora(zone, player, x, y); + break; + default: + player.notify(String.format("Sorry, minigame type '%s' is not supported.", type)); + return; + } + + // Let's get this party started! + zone.startMinigame(minigame); + } + +} diff --git a/gameserver/src/main/java/brainwine/gameserver/minigame/Minigame.java b/gameserver/src/main/java/brainwine/gameserver/minigame/Minigame.java new file mode 100644 index 00000000..2d0f371a --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/minigame/Minigame.java @@ -0,0 +1,206 @@ +package brainwine.gameserver.minigame; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; + +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.EntityAttack; +import brainwine.gameserver.item.ItemUseType; +import brainwine.gameserver.player.NotificationType; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.util.MathUtils; +import brainwine.gameserver.util.Vector2i; +import brainwine.gameserver.zone.Zone; + +/** + * Base class for minigame sessions. + */ +public abstract class Minigame { + + public static final long LEADERBOARD_UPDATE_INTERVAL = 2000; + protected final Map participants = new HashMap<>(); + protected final List leaderboard = new ArrayList<>(); + protected final Zone zone; + protected final Player initiator; + protected final int x; + protected final int y; + protected boolean active; + protected Participant currentLeader; + protected long startedAt; + private long lastLeaderboardUpdateAt; + + public Minigame(Zone zone, Player initiator, int x, int y) { + this.zone = zone; + this.initiator = initiator; + this.x = x; + this.y = y; + } + + public abstract void onInteract(Player player); + protected abstract void onStart(); + protected abstract void onFinish(); + + public void tick(float deltaTime) { + long now = System.currentTimeMillis(); + + // Update leaderboard + if(now >= lastLeaderboardUpdateAt + LEADERBOARD_UPDATE_INTERVAL) { + updateLeaderboard(); + lastLeaderboardUpdateAt = now; + } + } + + /** + * Preferably, call {@link Zone#startMinigame(Minigame)}. + */ + public final void start() { + if(active) { + return; // Do nothing if already started + } + + active = true; + startedAt = System.currentTimeMillis(); + addParticipant(initiator); + addParticipantsInRange(); + onStart(); + } + + public final void finish() { + if(!active) { + return; // Do nothing if already inactive + } + + updateLeaderboard(); + + // Clean up participants + for(Participant participant : participants.values()) { + Player player = participant.getPlayer(); + + if(player.getMinigame() == this) { + player.setMinigame(null); + } + + participant.showInfo(""); + } + + onFinish(); + active = false; + } + + public void entityKilled(Entity entity, EntityAttack cause) { + // Override + } + + public void entityAttacked(Entity entity, EntityAttack attack, float damage) { + // Override + } + + public void updateLeaderboard() { + leaderboard.clear(); + participants.values().stream().sorted((a, b) -> Double.compare(b.getScore(), a.getScore())).forEach(leaderboard::add); + Participant leader = leaderboard.get(0); + + if(leader.getScore() > 0.0) { + if(leader != currentLeader) { + notifyParticipants(String.format("%s took the lead with %s!", leader.getPlayer().getName(), describeScore(leader.getScore())), NotificationType.PEER_ACCOMPLISHMENT); + } + + currentLeader = leader; + leaderboard.forEach(x -> x.showInfo(String.format("You are in %s place with %s", ordinalizeNumber(getLeaderboardPosition(x)), describeScore(x.getScore())))); + } + } + + public void notifyCreator(Object message) { + notifyCreator(message, NotificationType.POPUP); + } + + public void notifyCreator(Object message, NotificationType type) { + if(initiator != null) { + initiator.notify(message, type); + } + } + + public void notifyParticipants(Object message) { + notifyParticipants(message, NotificationType.POPUP); + } + + public void notifyParticipants(Object message, NotificationType type) { + for(Participant participant : participants.values()) { + if(participant.isParticipating()) { + participant.getPlayer().notify(message, type); + } + } + } + + public void addParticipantsInRange() { + for(Player player : zone.getPlayersInRange(x, y, getRange())) { + if(!player.hasActiveMinigame()) { + addParticipant(player); + } + } + } + + public Participant addParticipant(Player player) { + player.setMinigame(this); + return participants.computeIfAbsent(player, x -> new Participant(this, x)); + } + + public boolean hasParticipant(Player player) { + return participants.containsKey(player); + } + + public Participant getParticipant(Player player) { + return participants.get(player); + } + + public int getLeaderboardPosition(Participant participant) { + return leaderboard.indexOf(participant) + 1; + } + + public String describeScore(double score) { + return String.format(Locale.US, "%,.2f points", score); // Override + } + + public double getRange() { + return Math.max(zone.getWidth(), zone.getHeight()); // Override + } + + public Vector2i getSpawnPoint(Player player) { + List spawnPoints = zone.getMetaBlocks().stream() + .filter(block -> block.getItem().hasUse(ItemUseType.TELEPORT) && MathUtils.inRange(x, y, block.getX(), block.getY(), getRange())) + .map(block -> new Vector2i(block.getX(), block.getY())) + .collect(Collectors.toCollection(ArrayList::new)); + spawnPoints.add(new Vector2i(x, y)); + return spawnPoints.get((int)(Math.random() * spawnPoints.size())); // Override + } + + public String ordinalizeNumber(int number) { + String str = String.valueOf(number); + String ordinal = str.endsWith("11") || str.endsWith("12") || str.endsWith("13") ? "th" : str.endsWith("1") ? "st" : str.endsWith("2") ? "nd" : str.endsWith("3") ? "rd" : "th"; + return String.format("%s%s", number, ordinal); + } + + public Zone getZone() { + return zone; + } + + public Player getInitiator() { + return initiator; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public boolean isActive() { + return active; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/minigame/Pandora.java b/gameserver/src/main/java/brainwine/gameserver/minigame/Pandora.java new file mode 100644 index 00000000..578d92f7 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/minigame/Pandora.java @@ -0,0 +1,316 @@ +package brainwine.gameserver.minigame; + +import static brainwine.shared.LogMarkers.SERVER_MARKER; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Random; +import java.util.Set; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.fasterxml.jackson.core.type.TypeReference; + +import brainwine.gameserver.GameServer; +import brainwine.gameserver.entity.Entity; +import brainwine.gameserver.entity.EntityAttack; +import brainwine.gameserver.entity.EntityRegistry; +import brainwine.gameserver.entity.npc.Npc; +import brainwine.gameserver.item.DamageType; +import brainwine.gameserver.item.Layer; +import brainwine.gameserver.player.NotificationType; +import brainwine.gameserver.player.Player; +import brainwine.gameserver.resource.ResourceFinder; +import brainwine.gameserver.util.Vector2i; +import brainwine.gameserver.zone.Zone; +import brainwine.shared.JsonHelper; + +public class Pandora extends Minigame { + + public static final String PANDORA_OPEN_ID = "containers/pandora-open"; + public static final String[] REWARD_LOOT_CATEGORIES = { "armaments", "armaments+", "treasure", "treasure+" }; // TODO make configurable + public static final long GRACE_PERIOD = 60000; // 1 minute + public static final long MAX_ROUND_DURATION = 480000; // 8 minutes + public static final double MINIGAME_RANGE = 50.0; + public static final double MAX_ENEMY_DISTANCE = 50.0; // Maximum distance wave enemies can wander before they're teleported back + public static final double MAX_RESPAWN_DISTANCE = 30.0; // Maximum distance at which players respawn near the minigame on death + public static final int MIN_ROUNDS = 10; + public static final int MAX_ROUNDS = 20; + private static final Logger logger = LogManager.getLogger(); + private static final Map>> config = new HashMap<>(); + private final Map roundSpawns = new HashMap<>(); + private final Set potencyBumps = new HashSet<>(); + private final List spawns = new ArrayList<>(); + private final Random random = new Random(); + private int currentRound; + private long roundStartedAt; + private long nextActionAt; + + public static void loadConfig() { + logger.info(SERVER_MARKER, "Loading Pandora configuration ..."); + + try { + config.clear(); + config.putAll(JsonHelper.readValue(ResourceFinder.getResourceUrl("pandora.json"), new TypeReference>>>(){})); + + // Make sure there are spawns for round 1 + if(!config.containsKey(1)) { + throw new IllegalArgumentException("No round 1 spawns configured"); + } + + // Perform extensive error checking on load so we don't have to do it later + config.forEach((round, spawns) -> { + // Check for empty spawns + if(spawns.stream().anyMatch(spawn -> spawn.values().stream().reduce(Integer::sum).orElse(0) <= 0)) { + throw new IllegalArgumentException(String.format("Round %s+ config has one or more empty spawns", round)); + } + + // Check for invalid entity types + String invalidType = spawns.stream() + .flatMap(spawn -> spawn.keySet().stream()) + .filter(type -> EntityRegistry.getEntityConfig(type) == null) + .findFirst().orElse(null); + + if(invalidType != null) { + throw new IllegalArgumentException(String.format("Invalid entity type: %s", invalidType)); + } + }); + } catch(Exception e) { + logger.error(SERVER_MARKER, "Failed to load Pandora config", e); + config.clear(); + return; + } + } + + public Pandora(Zone zone, Player initiator, int x, int y) { + super(zone, initiator, x, y); + } + + @Override + public void tick(float deltaTime) { + super.tick(deltaTime); + long now = System.currentTimeMillis(); + + // End minigame if block state is invalid + if(!zone.isChunkLoaded(x, y) || !zone.getBlock(x, y).getFrontItem().hasId(PANDORA_OPEN_ID)) { + finish(); + return; + } + + // Wait for grace period to end + if(currentRound == 0) { + if(now >= startedAt + GRACE_PERIOD) { + zone.notifyPlayers("Pandora's Box is coming ALIVE!"); + zone.updateBlock(x, y, Layer.FRONT, PANDORA_OPEN_ID, 2); + zone.spawnEffect(x, y, "karma sound", 1); + nextRound(); + } + + return; + } + + // Cancel minigame if the maximum round duration has been reached + if(now >= roundStartedAt + MAX_ROUND_DURATION) { + cancel("Pandora could not be contained. Better luck next time."); + return; + } + + // Perform an action if it is time + if(now >= nextActionAt) { + nextAction(); + nextActionAt = (long)(now + (0.5 + currentRound * 0.05) * 1000); // Spawns become less frequent as the difficulty increases + } + + // Randomly explode + if(Math.random() < deltaTime * 0.123) { + explode(); + } + } + + @Override + public void onInteract(Player player) { + addParticipant(player); + + // Increase potency if the minigame hasn't started yet + if(currentRound == 0 && potencyBumps.add(player.getDocumentId())) { + zone.notifyPlayers(String.format("%s increased Pandora's chaos level to %s!", player.getName(), potencyBumps.size()), NotificationType.PEER_ACCOMPLISHMENT); + } + } + + @Override + protected void onStart() { + // Reject if config failed to load + if(config.isEmpty()) { + notifyCreator(String.format("Pandora has not been configured correctly.\nPlease %s.", initiator.isAdmin() ? "check the server log for more info" : "contact a server administrator")); + finish(); + return; + } + + zone.spawnEffect(x, y, "match start", 1); + zone.updateBlock(x, y, Layer.FRONT, PANDORA_OPEN_ID, 1); + potencyBumps.add(initiator.getDocumentId()); + + // Notify all players in the zone + for(Player player : zone.getPlayers()) { + player.notifyProfile(String.format("%s opened Pandora's box at %s", initiator.getName(), zone.getReadableCoordinates(x, y)), "Tap on it in the next 60 seconds to build chaos!"); + } + } + + @Override + protected void onFinish() { + // Kill all spawned entities + for(Npc entity : spawns) { + entity.setMinigame(null); + entity.setHealth(0.0F); + } + + // Destroy block if it is still there + if(zone.getBlock(x, y).getFrontItem().hasId(PANDORA_OPEN_ID)) { + explode(8.0F); + zone.updateBlock(x, y, Layer.FRONT, 0); + zone.spawnEffect(x, y, "match end", 1); + } + } + + @Override + public void entityKilled(Entity entity, EntityAttack cause) { + spawns.remove(entity); + } + + @Override + public void entityAttacked(Entity entity, EntityAttack attack, float damage) { + // Do nothing if entity is not a wave enemy + if(!spawns.contains(entity)) { + return; + } + + Entity attacker = attack.getAttacker(); + + // Check if attacker is present + if(attacker == null || !attacker.isPlayer()) { + return; + }; + + Player player = (Player)attacker; + Participant participant = addParticipant(player); + participant.incrementScore(damage); + } + + @Override + public String describeScore(double score) { + return String.format(Locale.US, "%,.2f damage dealt", score); + } + + @Override + public double getRange() { + return MINIGAME_RANGE; + } + + @Override + public Vector2i getSpawnPoint(Player player) { + return player.inRange(x, y, MAX_RESPAWN_DISTANCE) ? super.getSpawnPoint(player) : null; + } + + private void nextAction() { + // Move on to the next round if all enemies have been killed + if(spawns.isEmpty() && roundSpawns.isEmpty()) { + nextRound(); + return; + } + + addParticipantsInRange(); + + // Spawn a random enemy if there are any remaining + if(!roundSpawns.isEmpty()) { + String entity = roundSpawns.keySet().stream().skip(random.nextInt(roundSpawns.size())).findAny().get(); + roundSpawns.compute(entity, (key, value) -> value <= 1 ? null : value - 1); + Npc npc = zone.spawnEntity(entity, x + random.nextInt(2), y - random.nextInt(3), true); + npc.setMinigame(this); + spawns.add(npc); + } + + // Teleport all out-of-range enemies back to the box + spawns.stream().filter(entity -> !entity.inRange(x, y, MAX_ENEMY_DISTANCE)).forEach(entity -> { + entity.spawnEffect("bomb-teleport", 4); + entity.setPosition(x + random.nextInt(2), y - random.nextInt(3)); + entity.spawnEffect("bomb-teleport", 4); + }); + } + + private void nextRound() { + addParticipantsInRange(); + int totalRounds = getTotalRounds(); + + // Finish if the final round has been cleared + if(currentRound >= totalRounds) { + complete(); + return; + } + + // Increment round and fetch spawn data + currentRound++; + int key = config.keySet().stream().filter(x -> currentRound >= x).max(Integer::compareTo).orElse(1); + List> configs = config.getOrDefault(key, Collections.emptyList()); + roundSpawns.clear(); + roundSpawns.putAll(configs.get(random.nextInt(configs.size()))); // Select a random wave of enemies + + // Randomly increase the number of spawns this round depending on the chaos level + int spawnBumps = potencyBumps.size() / (currentRound < 10 ? 2 : 3); + + for(int i = 0; i < spawnBumps; i++) { + Entry spawn = roundSpawns.entrySet().stream().skip(random.nextInt(roundSpawns.size())).findFirst().get(); + roundSpawns.put(spawn.getKey(), spawn.getValue() + 1); + } + + // Notify all players in the zone that the next round is starting + zone.notifyPlayers(String.format("Pandora wave %s of %s is beginning!", currentRound, totalRounds)); + roundStartedAt = System.currentTimeMillis(); + } + + private void complete() { + finish(); + double luckMultiplier = Math.min(10.0, potencyBumps.size()); + int baseLuck = Math.min(12, participants.size() * 4); + int position = 0; + + // Give out rewards + for(Participant participant : leaderboard) { + if(participant.isParticipating()) { + int luck = (int)(Math.max(1, baseLuck - position * 4) * luckMultiplier); + Player player = participant.getPlayer(); + player.awardLoot(GameServer.getInstance().getLootManager().getRandomLoot(luck, zone.getBiome(), player.getInventory().getWardrobe(), REWARD_LOOT_CATEGORIES)); + } + + position++; + } + + // Broadcast leader's score + zone.notifyPlayers(String.format("Pandora has been contained! %s showed mastery with %s!", currentLeader.getPlayer().getName(), describeScore(currentLeader.getScore()))); + } + + private void cancel(String message) { + finish(); + zone.notifyPlayers(message); + } + + private void explode() { + explode(4.0F + (float)Math.random()); + } + + private void explode(float radius) { + int x = this.x - 1 + (int)(Math.random() * 3); + int y = this.y - 1 + (int)(Math.random() * 3); + zone.explode(x, y, radius, null, false, 0.0F, DamageType.ENERGY, "bomb-electric"); // TODO explosion should do damage, but only to players! + } + + public int getTotalRounds() { + return Math.min(MAX_ROUNDS, MIN_ROUNDS + potencyBumps.size()); + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/minigame/Participant.java b/gameserver/src/main/java/brainwine/gameserver/minigame/Participant.java new file mode 100644 index 00000000..1a29309d --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/minigame/Participant.java @@ -0,0 +1,49 @@ +package brainwine.gameserver.minigame; + +import brainwine.gameserver.player.Player; +import brainwine.gameserver.server.messages.EventMessage; + +/** + * Model for keeping track of player scores in minigames. + */ +public class Participant { + + private final Minigame minigame; + private final Player player; + private double score; + + public Participant(Minigame minigame, Player player) { + this.minigame = minigame; + this.player = player; + } + + public void showInfo(String message) { + if(isParticipating()) { + player.sendMessage(new EventMessage("mini", message)); + } + } + + public boolean isParticipating() { + return player.isOnline() && player.getZone() == minigame.getZone(); + } + + public Player getPlayer() { + return player; + } + + public void incrementScore(double score) { + this.score += score; + } + + public void deductScore(double score) { + this.score -= score; + } + + public void setScore(double score) { + this.score = score; + } + + public double getScore() { + return score; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index 00c2a9e8..ae3b546b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -77,6 +77,7 @@ import brainwine.gameserver.server.pipeline.Connection; import brainwine.gameserver.util.MapHelper; import brainwine.gameserver.util.MathUtils; +import brainwine.gameserver.util.Vector2i; import brainwine.gameserver.util.VersionUtils; import brainwine.gameserver.zone.Biome; import brainwine.gameserver.zone.Block; @@ -683,13 +684,25 @@ public void showDialog(Dialog dialog) { } public void showDialog(Dialog dialog, Consumer handler) { + sendMessage(new DialogMessage(storeDialogHandler(handler), dialog)); + } + + public void showDialog(Map dialog) { + showDialog(dialog, null); + } + + public void showDialog(Map dialog, Consumer handler) { + sendMessage(new DialogMessage(storeDialogHandler(handler), dialog)); + } + + private int storeDialogHandler(Consumer handler) { int id = handler == null ? 0 : ++dialogDiscriminator; if(id != 0) { dialogs.put(id, handler); } - sendMessage(new DialogMessage(id, dialog)); + return id; } public void handleDialogInput(int id, Object[] input) { @@ -767,6 +780,21 @@ public void rubberband() { } public void respawn() { + // Check minigame spawnpoint + if(hasActiveMinigame()) { + Vector2i spawnPoint = minigame.getSpawnPoint(this); + + if(spawnPoint != null) { + respawn(spawnPoint.getX(), spawnPoint.getY()); + return; + } + } + + // Respawn at default spawn point + respawn(spawnX, spawnY); + } + + public void respawn(int x, int y) { if(isDead()) { setHealth(getMaxHealth()); breath = 1.0; @@ -774,9 +802,9 @@ public void respawn() { cold = 0.0; } - sendMessage(new PlayerPositionMessage(spawnX, spawnY)); + sendMessage(new PlayerPositionMessage(x, y)); sendMessageToPeers(new EntityStatusMessage(this, EntityStatus.REVIVED)); - zone.spawnEffect(spawnX + 0.5F, spawnY - 0.75F, "spawn", 20); + zone.spawnEffect(x + 0.5F, y - 0.75F, "spawn", 20); } /** @@ -853,6 +881,14 @@ public void notify(Object message) { notify(message, NotificationType.POPUP); } + public void notifyProfile(String title, String description) { + if(isV3()) { + notify(String.format("%s\n%s", title, description)); + } else { + notify(MapHelper.map(String.class, String.class, "title", title, "desc", description), NotificationType.PROFILE); + } + } + public void setHeldItem(Item item) { heldItem = item; } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java index 5d1fd6d2..82ae7fc9 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/EntityManager.java @@ -172,22 +172,22 @@ private boolean tryBustOrifice(int x, int y, Layer layer) { private void clearEntities() { npcs.values().stream() - .filter(npc -> npc.isDead() || (!npc.isPersistent() && (!zone.isChunkLoaded(npc.getBlockX(), npc.getBlockY()) || + .filter(npc -> npc.isDead() || (!npc.isPersistent() && !npc.hasActiveMinigame() && (!zone.isChunkLoaded(npc.getBlockX(), npc.getBlockY()) || (npc.isTransient() && System.currentTimeMillis() > npc.getLastTrackedAt() + ENTITY_CLEAR_TIME)))) .collect(Collectors.toList()) .forEach(this::removeEntity); } - public List getEntitiesInRange(float x, float y, float range) { + public List getEntitiesInRange(float x, float y, double range) { return getEntities().stream().filter(entity -> entity.inRange(x, y, range)).collect(Collectors.toList()); } - public Player getRandomPlayerInRange(float x, float y, float range) { + public Player getRandomPlayerInRange(float x, float y, double range) { List players = getPlayersInRange(x, y, range); return players.isEmpty() ? null : players.get(random.nextInt(players.size())); } - public List getPlayersInRange(float x, float y, float range) { + public List getPlayersInRange(float x, float y, double range) { return getPlayers().stream().filter(player -> player.inRange(x, y, range)).collect(Collectors.toList()); } diff --git a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java index ce975aad..f72540be 100644 --- a/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java +++ b/gameserver/src/main/java/brainwine/gameserver/zone/Zone.java @@ -9,6 +9,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -36,6 +37,7 @@ import brainwine.gameserver.item.Layer; import brainwine.gameserver.item.MetaType; import brainwine.gameserver.item.ModType; +import brainwine.gameserver.minigame.Minigame; import brainwine.gameserver.player.ChatType; import brainwine.gameserver.player.NotificationType; import brainwine.gameserver.player.Player; @@ -47,6 +49,7 @@ import brainwine.gameserver.server.messages.ConfigurationMessage; import brainwine.gameserver.server.messages.EffectMessage; import brainwine.gameserver.server.messages.LightMessage; +import brainwine.gameserver.server.messages.NotificationMessage; import brainwine.gameserver.server.messages.ZoneExploredMessage; import brainwine.gameserver.server.messages.ZoneStatusMessage; import brainwine.gameserver.server.models.BlockChangeData; @@ -61,6 +64,7 @@ */ public class Zone { + public static final int MAX_CONCURRENT_MINIGAMES = 20; public static final int DEFAULT_CHUNK_WIDTH = 20; public static final int DEFAULT_CHUNK_HEIGHT = 20; private static final ThreadLocalRandom random = ThreadLocalRandom.current(); @@ -103,6 +107,7 @@ public class Zone { private final Map fieldBlocks = new HashMap<>(); private final Map damageFieldBlocks = new HashMap<>(); private final Map blockChanges = new HashMap<>(); + private final Map minigames = new HashMap<>(); private final Map actionHistory = new HashMap<>(); private long lastStatusUpdate = System.currentTimeMillis(); private int ticksElapsed; @@ -184,6 +189,23 @@ public void tick(float deltaTime) { } } + // Update minigames + if(!minigames.isEmpty()) { + Iterator iterator = minigames.values().iterator(); + + while(iterator.hasNext()) { + Minigame minigame = iterator.next(); + + // Remove inactive minigames + if(!minigame.isActive()) { + iterator.remove(); + continue; + } + + minigame.tick(deltaTime); + } + } + // Process block timers if(!blockTimers.isEmpty()) { List> readyTimers = blockTimers.stream().filter(timer -> now >= timer.getTime()).collect(Collectors.toList()); @@ -296,6 +318,14 @@ public void sendChatMessage(Player sender, String text, ChatType type) { GameServer.getInstance().notify(String.format("%s: %s", sender.getName(), text), NotificationType.CHAT); } + public void notifyPlayers(String message) { + notifyPlayers(message, NotificationType.POPUP); + } + + public void notifyPlayers(String message, NotificationType type) { + sendMessage(new NotificationMessage(message, type)); + } + public void spawnEffect(float x, float y, String type, Object data) { sendLocalMessage(new EffectMessage(x, y, type, data), x, y); } @@ -1007,6 +1037,32 @@ public void processBlockTimer(int x, int y) { } } + public void startMinigame(Minigame minigame) { + int index = getBlockIndex(minigame.getX(), minigame.getY()); + Minigame currentMinigame = minigames.get(index); + + // Don't start minigame if a minigame is already active at this location + if(currentMinigame != null && currentMinigame.isActive()) { + minigame.notifyCreator("Another minigame is already in progress at that location."); + return; + } + + minigames.put(index, minigame); + minigame.start(); + } + + public Minigame getMinigame(int x, int y) { + return getMinigame(getBlockIndex(x, y)); + } + + public Minigame getMinigame(int index) { + return minigames.get(index); + } + + public int getMinigameCount() { + return minigames.size(); + } + public void updateBlock(int x, int y, Layer layer, int item) { updateBlock(x, y, layer, item, 0); } @@ -1284,15 +1340,15 @@ public Collection getGlobalMetaBlocks() { return Collections.unmodifiableCollection(globalMetaBlocks.values()); } - public List getEntitiesInRange(float x, float y, float range) { + public List getEntitiesInRange(float x, float y, double range) { return entityManager.getEntitiesInRange(x, y, range); } - public Player getRandomPlayerInRange(float x, float y, float range) { + public Player getRandomPlayerInRange(float x, float y, double range) { return entityManager.getRandomPlayerInRange(x, y, range); } - public List getPlayersInRange(float x, float y, float range) { + public List getPlayersInRange(float x, float y, double range) { return entityManager.getPlayersInRange(x, y, range); } @@ -1434,7 +1490,6 @@ public boolean areCoordinatesInBounds(int x, int y) { return x >= 0 && y >= 0 && x < width && y < height; } - // TODO move this function to ChunkManager protected void onChunkLoaded(Chunk chunk) { int chunkX = chunk.getX(); int chunkY = chunk.getY(); @@ -1470,13 +1525,19 @@ protected void onChunkLoaded(Chunk chunk) { // Simulate plant growth based on time passed since chunk was last loaded int cycles = (int)((System.currentTimeMillis() - chunk.getSaveTime()) / 1200000); // One cycle per 20 minutes growthManager.updateGrowables(cycles, growthSourceIndices); - } - // TODO move this function to ChunkManager - protected void onChunkUnloaded(Chunk chunk) { - // TODO is this function ever gonna be necessary? - // It seems that most (if not all) thingies are unindexed automatically. + protected void onChunkUnloaded(Chunk chunk) { + for(int x = 0; x < chunk.getWidth(); x++) { + for(int y = 0; y < chunk.getHeight(); y++) { + int index = getBlockIndex(chunk.getX() + x, chunk.getY() + y); + Minigame minigame = getMinigame(index); + + if(minigame != null) { + minigame.finish(); // Unload active minigame + } + } + } } public void saveChunks() { diff --git a/gameserver/src/main/resources/pandora.json b/gameserver/src/main/resources/pandora.json new file mode 100644 index 00000000..75287e97 --- /dev/null +++ b/gameserver/src/main/resources/pandora.json @@ -0,0 +1,146 @@ +{ + "1": [ + { + "creatures/crow-auto": 10 + }, + { + "creatures/roach-large": 10 + }, + { + "automata/tiny": 4, + "automata/small": 2 + }, + { + "revenant": 3 + }, + { + "terrapus/adult": 5, + "terrapus/fire": 1 + }, + { + "terrapus/fire": 4 + }, + { + "terrapus/frost": 4 + } + ], + "4": [ + { + "automata/tiny": 8, + "automata/small": 4 + }, + { + "brains/tiny-crawler": 3, + "brains/tiny-flyer": 3 + }, + { + "revenant": 4, + "dire-revenant": 1 + }, + { + "terrapus/adult": 8, + "terrapus/fire": 3 + }, + { + "terrapus/fire": 4, + "terrapus/frost": 4 + }, + { + "terrapus/fire": 3, + "brains/small": 1 + }, + { + "terrapus/skeleton": 4, + "terrapus/frost": 2 + } + ], + "7": [ + { + "automata/medium": 6, + "automata/large": 2 + }, + { + "brains/tiny-crawler": 7, + "brains/tiny-flyer": 6 + }, + { + "revenant": 5, + "dire-revenant": 3 + }, + { + "brains/small": 4, + "terrapus/frost": 3 + }, + { + "brains/medium": 1, + "brains/small": 2 + }, + { + "terrapus/skeleton": 8, + "terrapus/acid": 4 + } + ], + "10": [ + { + "automata/medium": 3, + "automata/large": 3 + }, + { + "revenant": 8, + "dire-revenant": 4 + }, + { + "brains/medium-dire": 1, + "brains/small": 3, + "brains/tiny-crawler": 4 + }, + { + "brains/medium": 3, + "brains/small": 4, + "brains/tiny-crawler": 3 + } + ], + "13": [ + { + "automata/medium": 4, + "automata/large": 5 + }, + { + "revenant": 10, + "dire-revenant": 5 + }, + { + "brains/medium-dire": 2, + "brains/small": 7 + }, + { + "brains/medium": 3, + "brains/small": 6 + }, + { + "terrapus/skeleton": 12, + "terrapus/acid": 8 + } + ], + "17": [ + { + "automata/large": 6 + }, + { + "brains/medium-dire": 4, + "brains/small": 2 + }, + { + "brains/medium-dire": 2, + "brains/medium": 5 + }, + { + "brains/large": 1, + "brains/medium": 2 + }, + { + "brains/large": 1, + "brains/small": 3 + } + ] +} From 1ebdd286df9d611a890e31009075fb50fe25d58e Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 22 Apr 2025 16:46:25 +0200 Subject: [PATCH 173/176] Add `PROFILE` notification type --- .../main/java/brainwine/gameserver/player/NotificationType.java | 1 + 1 file changed, 1 insertion(+) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/NotificationType.java b/gameserver/src/main/java/brainwine/gameserver/player/NotificationType.java index b372826a..1bad2741 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/NotificationType.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/NotificationType.java @@ -14,6 +14,7 @@ public enum NotificationType { PEER_ACCOMPLISHMENT(11), REWARD(12), // v2 only NOTE(13), // v2 only + PROFILE(16), // v2 only CHAT(20), LEVEL_UP(21), // v3 only ACHIEVEMENT(22), // v3 only From 6703dd59522ee61ad51c6d19e49bb4567fc87b35 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 22 Apr 2025 19:12:45 +0200 Subject: [PATCH 174/176] Custom loot dialog titles --- .../item/interactions/ContainerInteraction.java | 2 +- .../java/brainwine/gameserver/minigame/Pandora.java | 4 +++- .../java/brainwine/gameserver/player/Player.java | 12 ++++++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java index 5f63d400..cda7e526 100644 --- a/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java +++ b/gameserver/src/main/java/brainwine/gameserver/item/interactions/ContainerInteraction.java @@ -76,7 +76,7 @@ public void interact(Zone zone, Entity entity, int x, int y, Layer layer, Item i metaBlock.removeProperty("xp"); } - player.awardLoot(loot, item.getLootGraphic()); + player.awardLoot(loot, item.getLootGraphic(), "You found:"); player.addExperience(experience); player.getStatistics().trackContainerLooted(item); } else { diff --git a/gameserver/src/main/java/brainwine/gameserver/minigame/Pandora.java b/gameserver/src/main/java/brainwine/gameserver/minigame/Pandora.java index 578d92f7..a0e61f25 100644 --- a/gameserver/src/main/java/brainwine/gameserver/minigame/Pandora.java +++ b/gameserver/src/main/java/brainwine/gameserver/minigame/Pandora.java @@ -25,6 +25,7 @@ import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.item.DamageType; import brainwine.gameserver.item.Layer; +import brainwine.gameserver.loot.Loot; import brainwine.gameserver.player.NotificationType; import brainwine.gameserver.player.Player; import brainwine.gameserver.resource.ResourceFinder; @@ -285,7 +286,8 @@ private void complete() { if(participant.isParticipating()) { int luck = (int)(Math.max(1, baseLuck - position * 4) * luckMultiplier); Player player = participant.getPlayer(); - player.awardLoot(GameServer.getInstance().getLootManager().getRandomLoot(luck, zone.getBiome(), player.getInventory().getWardrobe(), REWARD_LOOT_CATEGORIES)); + Loot loot = GameServer.getInstance().getLootManager().getRandomLoot(luck, zone.getBiome(), player.getInventory().getWardrobe(), REWARD_LOOT_CATEGORIES); + player.awardLoot(loot, String.format("You won %s place!", ordinalizeNumber(position + 1))); } position++; diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index ae3b546b..a4cd54e1 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -1566,6 +1566,14 @@ public void awardLoot(Loot loot) { } public void awardLoot(Loot loot, DialogType dialogType) { + awardLoot(loot, dialogType, "You received:"); + } + + public void awardLoot(Loot loot, String title) { + awardLoot(loot, DialogType.LOOT, title); + } + + public void awardLoot(Loot loot, DialogType dialogType, String title) { Dialog dialog = new Dialog(); DialogSection section = new DialogSection(); dialog.addSection(section); @@ -1592,10 +1600,10 @@ public void awardLoot(Loot loot, DialogType dialogType) { } if(v3) { - dialog.setTitle("You found:"); + dialog.setTitle(title); showDialog(dialog.setType(dialogType)); } else { - section.setTitle("You found:"); + section.setTitle(title); notify(dialog, NotificationType.REWARD); } } From 965117adeae51631e0367e6d8fa3bf10334c20ea Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Tue, 22 Apr 2025 20:36:59 +0200 Subject: [PATCH 175/176] Pandora loot `null` check --- .../main/java/brainwine/gameserver/minigame/Pandora.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/minigame/Pandora.java b/gameserver/src/main/java/brainwine/gameserver/minigame/Pandora.java index a0e61f25..96108156 100644 --- a/gameserver/src/main/java/brainwine/gameserver/minigame/Pandora.java +++ b/gameserver/src/main/java/brainwine/gameserver/minigame/Pandora.java @@ -287,7 +287,12 @@ private void complete() { int luck = (int)(Math.max(1, baseLuck - position * 4) * luckMultiplier); Player player = participant.getPlayer(); Loot loot = GameServer.getInstance().getLootManager().getRandomLoot(luck, zone.getBiome(), player.getInventory().getWardrobe(), REWARD_LOOT_CATEGORIES); - player.awardLoot(loot, String.format("You won %s place!", ordinalizeNumber(position + 1))); + + if(loot != null) { + player.awardLoot(loot, String.format("You won %s place!", ordinalizeNumber(position + 1))); + } else { + player.notify("Sorry, we couldn't find a suitable reward for you."); + } } position++; From 5e4de719ddfb3c3ba92dd96b24c58b30e2f27482 Mon Sep 17 00:00:00 2001 From: kuroppoi <68156848+kuroppoi@users.noreply.github.com> Date: Fri, 25 Apr 2025 04:40:04 +0200 Subject: [PATCH 176/176] Allow players in god mode to bypass Pandora potency bump check --- .../java/brainwine/gameserver/minigame/Pandora.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/minigame/Pandora.java b/gameserver/src/main/java/brainwine/gameserver/minigame/Pandora.java index 96108156..a66db40c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/minigame/Pandora.java +++ b/gameserver/src/main/java/brainwine/gameserver/minigame/Pandora.java @@ -51,6 +51,7 @@ public class Pandora extends Minigame { private final List spawns = new ArrayList<>(); private final Random random = new Random(); private int currentRound; + private int potencyLevel; private long roundStartedAt; private long nextActionAt; @@ -140,8 +141,8 @@ public void onInteract(Player player) { addParticipant(player); // Increase potency if the minigame hasn't started yet - if(currentRound == 0 && potencyBumps.add(player.getDocumentId())) { - zone.notifyPlayers(String.format("%s increased Pandora's chaos level to %s!", player.getName(), potencyBumps.size()), NotificationType.PEER_ACCOMPLISHMENT); + if(currentRound == 0 && (potencyBumps.add(player.getDocumentId()) || player.isGodMode())) { + zone.notifyPlayers(String.format("%s increased Pandora's chaos level to %s!", player.getName(), ++potencyLevel), NotificationType.PEER_ACCOMPLISHMENT); } } @@ -157,6 +158,7 @@ protected void onStart() { zone.spawnEffect(x, y, "match start", 1); zone.updateBlock(x, y, Layer.FRONT, PANDORA_OPEN_ID, 1); potencyBumps.add(initiator.getDocumentId()); + potencyLevel++; // Notify all players in the zone for(Player player : zone.getPlayers()) { @@ -263,7 +265,7 @@ private void nextRound() { roundSpawns.putAll(configs.get(random.nextInt(configs.size()))); // Select a random wave of enemies // Randomly increase the number of spawns this round depending on the chaos level - int spawnBumps = potencyBumps.size() / (currentRound < 10 ? 2 : 3); + int spawnBumps = potencyLevel / (currentRound < 10 ? 2 : 3); for(int i = 0; i < spawnBumps; i++) { Entry spawn = roundSpawns.entrySet().stream().skip(random.nextInt(roundSpawns.size())).findFirst().get(); @@ -277,7 +279,7 @@ private void nextRound() { private void complete() { finish(); - double luckMultiplier = Math.min(10.0, potencyBumps.size()); + double luckMultiplier = Math.min(10.0, potencyLevel); int baseLuck = Math.min(12, participants.size() * 4); int position = 0; @@ -318,6 +320,6 @@ private void explode(float radius) { } public int getTotalRounds() { - return Math.min(MAX_ROUNDS, MIN_ROUNDS + potencyBumps.size()); + return Math.min(MAX_ROUNDS, MIN_ROUNDS + potencyLevel); } }