From 6b095481afeeb7cd7dbb5d4a68476ea087756d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Sat, 1 Nov 2025 22:34:51 +0100 Subject: [PATCH 1/3] Prevent appearance reset if the slot actually doesn't have the lost exoskeleton part --- .../main/java/brainwine/gameserver/player/Inventory.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java index 246a60b2..92588a9b 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java @@ -162,12 +162,15 @@ private void setItem(Item item, int quantity, boolean sendMessage) { // Unequip appearance item // TODO: potential nullptr if appearance value is null - if(slot != null && player.getAppearance().getOrDefault(slot.getId(), 0).equals(item.getCode())) { - player.updateAppearance(MapHelper.map(slot.getId(), 0)); + if(slot != null) { + Object oldAppearance = player.getAppearance().getOrDefault(slot.getId(), 0); + if(oldAppearance.equals(true) || oldAppearance.equals(item.getCode())) { + player.updateAppearance(MapHelper.map(slot.getId(), 0)); + } } } else { // Equip appearance item (unless player already has it) - if(slot != null && !hasItem(item)) { + if(slot != null && !hasItem(item) && player.getAppearance().getOrDefault(slot.getId(), 0).equals(0)) { player.updateAppearance(MapHelper.map(slot.getId(), item.getCode())); } From 3e70456a34433e992f97a2ed70e50da8980300a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Sun, 2 Nov 2025 22:16:31 +0100 Subject: [PATCH 2/3] merge appearance update code --- .../gameserver/anticheat/AnticheatConfig.java | 21 +++++++---- .../gameserver/anticheat/Exoskeleton.java | 13 +++++++ .../gameserver/player/Inventory.java | 35 +++++++++++-------- .../brainwine/gameserver/player/Player.java | 5 ++- 4 files changed, 52 insertions(+), 22 deletions(-) create mode 100644 gameserver/src/main/java/brainwine/gameserver/anticheat/Exoskeleton.java diff --git a/gameserver/src/main/java/brainwine/gameserver/anticheat/AnticheatConfig.java b/gameserver/src/main/java/brainwine/gameserver/anticheat/AnticheatConfig.java index afa60c88..f354ee54 100644 --- a/gameserver/src/main/java/brainwine/gameserver/anticheat/AnticheatConfig.java +++ b/gameserver/src/main/java/brainwine/gameserver/anticheat/AnticheatConfig.java @@ -3,23 +3,30 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class AnticheatConfig { - @JsonProperty("exploder_farm") - private ExploderFarm exploderFarm = new ExploderFarm(); - @JsonProperty("afk_entity_spawn") private AfkEntitySpawn afkEntitySpawn = new AfkEntitySpawn(); + @JsonProperty("exoskeleton") + private Exoskeleton exoskeleton = new Exoskeleton(); + + @JsonProperty("exploder_farm") + private ExploderFarm exploderFarm = new ExploderFarm(); + @JsonProperty("exploration") private Exploration exploration = new Exploration(); - public ExploderFarm getExploderFarm() { - return exploderFarm; - } - public AfkEntitySpawn getAfkEntitySpawn() { return afkEntitySpawn; } + public Exoskeleton getExoskeleton() { + return exoskeleton; + } + + public ExploderFarm getExploderFarm() { + return exploderFarm; + } + public Exploration getExploration() { return exploration; } diff --git a/gameserver/src/main/java/brainwine/gameserver/anticheat/Exoskeleton.java b/gameserver/src/main/java/brainwine/gameserver/anticheat/Exoskeleton.java new file mode 100644 index 00000000..be2d7ea6 --- /dev/null +++ b/gameserver/src/main/java/brainwine/gameserver/anticheat/Exoskeleton.java @@ -0,0 +1,13 @@ +package brainwine.gameserver.anticheat; + +import brainwine.gameserver.item.InventoryType; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Exoskeleton { + @JsonProperty + public InventoryType inventoryType = InventoryType.ACCESSORY; + + public InventoryType getInventoryType() { + return inventoryType; + } +} diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java index 92588a9b..d0badaa4 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java @@ -11,6 +11,8 @@ import java.util.stream.Collectors; import brainwine.gameserver.GameServer; +import brainwine.gameserver.anticheat.AnticheatManager; +import brainwine.gameserver.anticheat.Exoskeleton; import brainwine.gameserver.item.Action; import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.zone.ZoneActivity; @@ -82,12 +84,22 @@ public void moveItemToContainer(Item item, ContainerType type, int slot) { case ACCESSORIES: Item currentItem = accessories.getItem(slot); if("prosthetics".equals(currentItem.getCategory())) { - exoskeletonUpdated = true; + if(AnticheatManager.getConfig().getExoskeleton().getInventoryType() == InventoryType.ACCESSORY) { + Object setting = player.getAppearance().get(currentItem.getAppearanceSlot().getId()); + if(setting instanceof Integer && ItemRegistry.getItem((int)setting).equals(currentItem)) { + player.updateAppearance(MapHelper.map(currentItem.getAppearanceSlot().getId(), true)); + } + } } accessories.moveItem(item, slot); accessoriesUpdated = true; if("prosthetics".equals(item.getCategory())) { - exoskeletonUpdated = true; + if(AnticheatManager.getConfig().getExoskeleton().getInventoryType() == InventoryType.ACCESSORY) { + Object setting = player.getAppearance().get(item.getAppearanceSlot().getId()); + if(setting instanceof Integer && ItemRegistry.getItem((int)setting).equals(item)) { + player.updateAppearance(MapHelper.map(item.getAppearanceSlot().getId(), true)); + } + } } break; } @@ -95,9 +107,6 @@ public void moveItemToContainer(Item item, ContainerType type, int slot) { if(accessoriesUpdated) { Map statusConfig = player.getStatusConfig(); player.sendMessageToPeers(new EntityChangeMessage(player.getId(), statusConfig)); - if(exoskeletonUpdated) { - player.sendMessage(new EntityChangeMessage(player.getId(), statusConfig)); - } } } @@ -124,12 +133,6 @@ public void addItem(Item item, int quantity, boolean sendMessage) { finalQuantity = Math.max(currentQuantity, Math.min(finalQuantity, allowed)); } setItem(item, finalQuantity, sendMessage); - - if(currentQuantity == 0 && finalQuantity > 0) { - if(item.hasId("accessories/makeup")) { - player.sendMessage(new WardrobeMessage(getClientWardrobe())); - } - } } public void removeItem(Item item) { @@ -170,9 +173,14 @@ private void setItem(Item item, int quantity, boolean sendMessage) { } } else { // Equip appearance item (unless player already has it) - if(slot != null && !hasItem(item) && player.getAppearance().getOrDefault(slot.getId(), 0).equals(0)) { + if(slot != null && !hasItem(item) && (!"prosthetics".equals(item.getCategory()) || AnticheatManager.getConfig().getExoskeleton().getInventoryType() == InventoryType.HIDDEN) && player.getAppearance().getOrDefault(slot.getId(), 0).equals(0)) { player.updateAppearance(MapHelper.map(slot.getId(), item.getCode())); } + + // Send wardrobe message with the new available colors if the player is newly obtaining a makeup kit + if(item.hasId("accessories/makeup") && !hasItem(item)) { + player.sendMessage(new WardrobeMessage(getClientWardrobe())); + } items.put(item, quantity); } @@ -284,8 +292,7 @@ public Map getJsonValue() { private void addItemLocation(Item item, List itemData) { int slot = -1; - // TODO use exo behavior switch here if the hidden slots work in stock v3 - if(item.isHidden() && (!"prosthetics".equals(item.getCategory()) || (!player.isV3() && accessories.getSlot(item) != -1))) { + if(item.isHidden() && (!(AnticheatManager.getConfig().getExoskeleton().getInventoryType() == InventoryType.ACCESSORY && "prosthetics".equals(item.getCategory())) || !player.isV3())) { itemData.add("z"); itemData.add(ItemRegistry.getHiddenItemIndex(item)); } else if((slot = hotbar.getSlot(item)) != -1) { diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Player.java b/gameserver/src/main/java/brainwine/gameserver/player/Player.java index b80967dc..cc026bc0 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Player.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Player.java @@ -19,6 +19,8 @@ import java.util.stream.Collectors; import brainwine.gameserver.androidshop.AndroidShopHistory; +import brainwine.gameserver.anticheat.AnticheatManager; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -41,6 +43,7 @@ import brainwine.gameserver.entity.npc.Npc; import brainwine.gameserver.item.Action; import brainwine.gameserver.item.DamageType; +import brainwine.gameserver.item.InventoryType; import brainwine.gameserver.item.Item; import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.item.ItemUseType; @@ -1710,7 +1713,7 @@ public Map getCustomizedAppearance() { Item[] selected = new Item[customizableAppearanceSlots.size()]; - for(Item accessory: getInventory().getAccessories()) { + for(Item accessory: getInventory().getAccessories(AnticheatManager.getConfig().getExoskeleton().getInventoryType() == InventoryType.HIDDEN)) { if("prosthetics".equals(accessory.getCategory())) { AppearanceSlot slot = accessory.getAppearanceSlot(); From f168ecaec160894c01396caac01de2f0931a1baf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mert=20Bora=20=C4=B0nevi?= Date: Thu, 4 Dec 2025 02:21:02 +0100 Subject: [PATCH 3/3] fix bugs with exoskeleton updating --- .../gameserver/player/Inventory.java | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java index d0badaa4..51b3e14c 100644 --- a/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java +++ b/gameserver/src/main/java/brainwine/gameserver/player/Inventory.java @@ -12,7 +12,6 @@ import brainwine.gameserver.GameServer; import brainwine.gameserver.anticheat.AnticheatManager; -import brainwine.gameserver.anticheat.Exoskeleton; import brainwine.gameserver.item.Action; import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.zone.ZoneActivity; @@ -22,7 +21,6 @@ import brainwine.gameserver.item.InventoryType; import brainwine.gameserver.item.Item; -import brainwine.gameserver.item.ItemRegistry; import brainwine.gameserver.item.ItemUseType; import brainwine.gameserver.server.messages.EntityChangeMessage; import brainwine.gameserver.server.messages.InventoryMessage; @@ -83,24 +81,30 @@ public void moveItemToContainer(Item item, ContainerType type, int slot) { break; case ACCESSORIES: Item currentItem = accessories.getItem(slot); + Map appearanceUpdates = new HashMap<>(); if("prosthetics".equals(currentItem.getCategory())) { + // Unequipping exoskeleton part if(AnticheatManager.getConfig().getExoskeleton().getInventoryType() == InventoryType.ACCESSORY) { Object setting = player.getAppearance().get(currentItem.getAppearanceSlot().getId()); if(setting instanceof Integer && ItemRegistry.getItem((int)setting).equals(currentItem)) { - player.updateAppearance(MapHelper.map(currentItem.getAppearanceSlot().getId(), true)); + appearanceUpdates.put(currentItem.getAppearanceSlot().getId(), true); } } } accessories.moveItem(item, slot); accessoriesUpdated = true; if("prosthetics".equals(item.getCategory())) { + // Equipping new exoskeleton part if(AnticheatManager.getConfig().getExoskeleton().getInventoryType() == InventoryType.ACCESSORY) { Object setting = player.getAppearance().get(item.getAppearanceSlot().getId()); - if(setting instanceof Integer && ItemRegistry.getItem((int)setting).equals(item)) { - player.updateAppearance(MapHelper.map(item.getAppearanceSlot().getId(), true)); + if(setting == null || setting instanceof Boolean || setting.equals(0)) { + appearanceUpdates.put(item.getAppearanceSlot().getId(), setting != null && !setting.equals(0) ? setting : true); } } } + if(!appearanceUpdates.isEmpty()) { + player.updateAppearance(appearanceUpdates); + } break; } @@ -167,8 +171,12 @@ private void setItem(Item item, int quantity, boolean sendMessage) { // TODO: potential nullptr if appearance value is null if(slot != null) { Object oldAppearance = player.getAppearance().getOrDefault(slot.getId(), 0); - if(oldAppearance.equals(true) || oldAppearance.equals(item.getCode())) { - player.updateAppearance(MapHelper.map(slot.getId(), 0)); + if(oldAppearance.equals(item.getCode())) { + if(item.getId().startsWith("prosthetics/")) { + player.updateAppearance(MapHelper.map(slot.getId(), true)); + } else { + player.updateAppearance(MapHelper.map(slot.getId(), 0)); + } } } } else { @@ -244,9 +252,9 @@ public List getAccessories() { public List getAccessories(boolean includeHidden) { List items = new ArrayList<>(); - + for(Item item : accessories.getItems()) { - if(item.isAccessory()) { + if(item.isAccessory() || item.getId().startsWith("prosthetics/")) { items.add(item); } } @@ -292,7 +300,7 @@ public Map getJsonValue() { private void addItemLocation(Item item, List itemData) { int slot = -1; - if(item.isHidden() && (!(AnticheatManager.getConfig().getExoskeleton().getInventoryType() == InventoryType.ACCESSORY && "prosthetics".equals(item.getCategory())) || !player.isV3())) { + if(item.isHidden() && (!player.isV3() || (AnticheatManager.getConfig().getExoskeleton().getInventoryType() == InventoryType.HIDDEN || !"prosthetics".equals(item.getCategory())))) { itemData.add("z"); itemData.add(ItemRegistry.getHiddenItemIndex(item)); } else if((slot = hotbar.getSlot(item)) != -1) {