I am working on a multiplayer game, where a player has many storages to store items in.
Storage
Storage is the system that allows the user to deposit items, and take them back. Example of storage types:
- Inventory: Same as WoW inventory, you can add items, drop items, move items from slot to slot.
- Bank: Bank is what you can call, a super-inventory, which has around 300 items limit per tab Bank can have maximum of 10 tabs which will store items. total items allowed in the bank is 3000 (10 tabs multiplied by 300 items per tab). How ever, in the bank, all items are stackable.
- Equipment: Equipment is the same as Inventory, just with sorted slots, i.e Helm, Body, Legs and there are total of 8 slots.
- Chests and other: Same as Bank just without tabs.
An item in-game
Each item has it's own item id. Only certain items can be stackable, unless the storage type is Bank or chest.
I have created a system which will make it easier for me to manage storages, and there's the abstract class, that each storage type class will extend it:
public abstract class PlayerItemStorage {
private int size;
/**
* The items
*/
private PlayerItem[] items;
/**
* Sometimes, by default, the storage items are id + 1, due to
* the client's interfaces. No exact explanation on it, but the only
* feature that doesn't do that is equipment and possible BoB
*/
private boolean increasedItem = true;
public PlayerItemStorage(int storageSize) {
this.items = new PlayerItem[storageSize];
this.size = storageSize;
}
public PlayerItemStorage(PlayerItem[] items) {
this.items = items;
this.size = items.length;
}
/**
* Resets the storage
*/
public void resetStorage() {
this.items = new PlayerItem[this.size];
}
/**
* Gets the item list
* @return PlayerItem list
*/
public PlayerItem[] getItems() {
return this.items;
}
/**
* Counts how many items there are with the same id.
* @param id Item id
* @return count
*/
public int count(int id) {
int count = 0;
for (PlayerItem item : this.items) {
if (this.getId(item) == id) {
count++;
}
}
return count;
}
/**
* Counts how many items there are in the inventory
* not including amount, just the item objects themself.
* @return amount
*/
public int countItems() {
int count = 0;
for (PlayerItem item : this.items) {
if (this.getId(item) > 0 && item.getAmount() > 0) {
count++;
}
}
return count;
}
/**
* Gets the length of the storage
* @return
*/
public int getLength() {
return this.items.length;
}
/**
* Checks how many free slots the storage has
* @return
*/
public int freeSlots() {
int slots = 0;
for (PlayerItem item : this.items) {
if (this.getId(item) <= 0) {
slots++;
}
}
return slots;
}
/**
* Finds an item id in the earliest slot.
* @param id Item ID
* @return Slot id
*/
public int find(int id) {
for (int i = 0; i < this.items.length; i++) {
PlayerItem item = this.items[i];
if ( this.getId(item) == id && item.getAmount() > 0) {
return i;
}
}
return -1;
}
/**
* Gets the earliest free slot
* @return slot id
*/
public int getFreeSlot() {
for (int i = 0; i < this.items.length; i++) {
if (this.getId(i) <= 0) {
return i;
}
}
return -1;
}
/**
* Checks if item is existing in the given slot, with the given amount and
* item id.
* @param id Item ID
* @param amount Item Amount
* @param slot Slot id
* @return boolean
*/
public boolean containsInSlot(int id, int amount, int slot) {
int found = 0;
if (this.items[slot].getAlpha() == id) {
for (int i = 0; i < this.items.length; i++) {
PlayerItem item = this.items[i];
if (this.getId(item) == id) {
if (item.getAmount() >= amount) {
return true;
} else {
found++;
}
}
}
if (found >= amount) {
return true;
}
return false;
}
return false;
}
/**
* Checks if item exists in the storage
*/
public boolean contains(int id) {
for (PlayerItem item : this.items) {
if (this.getId(item) == id) {
return true;
}
}
return false;
}
/**
* {@link #increasedItem}
* @param i Index
* @return real item id
*/
private int getId(int i) {
if (this.increasedItem) {
return this.items[i].getAlpha();
}
return this.items[i].getId();
}
/**
* {@link #increasedItem}
* @param item PlayerItem object
* @return real item id
*/
private int getId(PlayerItem item) {
if (this.increasedItem) {
return item.getAlpha();
}
return item.getId();
}
public abstract boolean add(int id, int amount);
public abstract boolean remove(int id, int amount, int fromSlot);
}
I also have a container class which is in charge to control all storages:
/**
* Container class for all storages of a player
* @author Ben
*/
public class PlayerStorageContainer {
/**
* Player object (Client extends Player extends Entity)
*/
public Player player;
/**
* boolCatach: If enabled, when removing/adding item to ALL of the storages,
* it will check if the action returned false, if yes it will stop right away and
* return false.
*/
private boolean boolCatch;
/**
* All of the storages the player will contain
*/
private Map<String, PlayerItemStorage> storages =
new HashMap<String, PlayerItemStorage>();
public PlayerStorageContainer(Player p) {
this.player = p;
this.initializePlayerStorages();
}
/**
* Initializes the existing storage for player.
*/
private void initializePlayerStorages() {
this.storages.put("inventory", new Inventory(this.player));
}
/**
* Gets the required PlayerItemStorage implementation
* @param name Name of the implementing object
* @return The object that extends PlayerItemStorage
*/
public PlayerItemStorage getStorage(String name) {
return this.storages.get(name);
}
/**
* Removes an item from ALL storages
* @param id The Item ID
* @param amount The amount of the item
* @return Returns a boolean if action was successful without any falses in the
* middle of the execution, will return always true if {!boolCatch}
*/
public boolean remove(int id, int amount) {
for (PlayerItemStorage storage : this.storages.values()) {
if (!storage.remove(id, amount) && this.boolCatch) {
return false;
}
}
return true;
}
/**
* Adds an item to ALL storages
* @param id The Item ID
* @param amount The amount of the item
* @return Returns a boolean if action was successful without any falses in the
* middle of the execution, will return always true if {!boolCatch}
*/
public boolean add(int id, int amount) {
for (PlayerItemStorage storage : this.storages.values()) {
if (!storage.add(id, amount) && this.boolCatch) {
return false;
}
}
return true;
}
/**
* Resets all storages
*/
public void reset() {
for (PlayerItemStorage storage : this.storages.values()) {
storage.resetStorage();
}
}
/**
* Toogles {@link PlayerItemContainer#boolCatch} for errors
* @param b
*/
public void setErrorCatch(boolean b) {
this.boolCatch = b;
}
}
The problem
This system works fine for inventory, and equipment - however, this will be a problem and cause some big algorithm code duplicating for the bank type.
Since bank type storage can have multiple tabs up to 10, we will either need to create class named Tab and make it implement PlayerItemStorage, and put the same algorithm over the two classes (Bank and Tab) or have one method in Bank, and then have a variable named "focusedTab" which will hold the selected tab id, and make the add method accept 3 arguements (itemId, amount, tabId).
But making it accept 3 arguments will make a problem, I'll have to either ignore the abstract method add, and make own method for it, or create 2 abstract adds method, one with 2 parameters, one with 3 which I find too messy.
Did I design this whole thing incorrectly that I am having these problems? How can I design a proper structure for this whole storage feature so it works with all kind of storages I can think of?
If you cannot understand the problem, you might find my code review thread helpful
Background about how the server handles items
When the client clicks on any item, in any game interface that supports item storage, it sends the slot id the player clicked on, due to security reasons. The server uses the slot id as the index of the PlayerItem[] array to get the information about the item.