diff --git a/src/main/java/emu/nebula/command/commands/CleanCommand.java b/src/main/java/emu/nebula/command/commands/CleanCommand.java index 9bdf2ae..796055a 100644 --- a/src/main/java/emu/nebula/command/commands/CleanCommand.java +++ b/src/main/java/emu/nebula/command/commands/CleanCommand.java @@ -5,8 +5,6 @@ import emu.nebula.command.CommandArgs; import emu.nebula.command.CommandHandler; import emu.nebula.data.GameData; import emu.nebula.data.resources.ItemDef; -import emu.nebula.game.inventory.GameItem; -import emu.nebula.game.inventory.GameResource; import emu.nebula.game.inventory.ItemParamMap; import emu.nebula.game.inventory.ItemType; import emu.nebula.game.player.PlayerChangeInfo; @@ -64,14 +62,14 @@ public class CleanCommand implements CommandHandler { if (all) { if (doItems) { - for (GameItem item : inv.getItems().values()) { - removeMap.add(item.getItemId(), item.getCount()); + for (var entry : inv.getItems().int2IntEntrySet()) { + removeMap.add(entry.getIntKey(), entry.getIntValue()); } } if (doResources) { - for (GameResource res : inv.getResources().values()) { - removeMap.add(res.getResourceId(), res.getCount()); + for (var entry : inv.getResources().int2IntEntrySet()) { + removeMap.add(entry.getIntKey(), entry.getIntValue()); } } } else { diff --git a/src/main/java/emu/nebula/database/DatabaseManager.java b/src/main/java/emu/nebula/database/DatabaseManager.java index 5da9f2f..20303f4 100644 --- a/src/main/java/emu/nebula/database/DatabaseManager.java +++ b/src/main/java/emu/nebula/database/DatabaseManager.java @@ -212,6 +212,14 @@ public final class DatabaseManager { update(obj, uid, field2, value2); } + public void updateUnset(Object obj, int uid, String field) { + var opt = new UpdateOptions().upsert(false); + + getDatastore().find(obj.getClass()) + .filter(Filters.eq("_id", uid)) + .update(opt, UpdateOperators.unset(field)); + } + public void updateNested(Object obj, int uid, String filter, int filterId, String field, Object item) { var opt = new UpdateOptions().upsert(false); diff --git a/src/main/java/emu/nebula/game/inventory/GameItem.java b/src/main/java/emu/nebula/game/inventory/GameItem.java index 7edbbb1..f1353f8 100644 --- a/src/main/java/emu/nebula/game/inventory/GameItem.java +++ b/src/main/java/emu/nebula/game/inventory/GameItem.java @@ -12,6 +12,7 @@ import emu.nebula.proto.Public.Item; import emu.nebula.util.Utils; import lombok.Getter; +@Deprecated @Getter @Entity(value = "items", useDiscriminator = false) public class GameItem implements GameDatabaseObject { diff --git a/src/main/java/emu/nebula/game/inventory/GameResource.java b/src/main/java/emu/nebula/game/inventory/GameResource.java index 2568709..cec883c 100644 --- a/src/main/java/emu/nebula/game/inventory/GameResource.java +++ b/src/main/java/emu/nebula/game/inventory/GameResource.java @@ -12,6 +12,7 @@ import emu.nebula.proto.Public.Res; import emu.nebula.util.Utils; import lombok.Getter; +@Deprecated @Getter @Entity(value = "resources", useDiscriminator = false) public class GameResource implements GameDatabaseObject { diff --git a/src/main/java/emu/nebula/game/inventory/Inventory.java b/src/main/java/emu/nebula/game/inventory/Inventory.java index 89d8531..de2cb6f 100644 --- a/src/main/java/emu/nebula/game/inventory/Inventory.java +++ b/src/main/java/emu/nebula/game/inventory/Inventory.java @@ -2,6 +2,8 @@ package emu.nebula.game.inventory; import java.util.List; +import com.mongodb.client.model.Filters; + import dev.morphia.annotations.Entity; import dev.morphia.annotations.Id; import emu.nebula.GameConstants; @@ -20,12 +22,11 @@ import emu.nebula.proto.Public.Item; import emu.nebula.proto.Public.Res; import emu.nebula.proto.Public.Title; import emu.nebula.proto.Public.UI32; +import emu.nebula.util.Utils; import emu.nebula.util.ints.String2IntMap; import emu.nebula.game.achievement.AchievementCondition; import emu.nebula.game.player.Player; import emu.nebula.game.player.PlayerChangeInfo; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntCollection; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; @@ -37,6 +38,10 @@ public class Inventory extends PlayerManager implements GameDatabaseObject { @Id private int uid; + // Items/resources + private ItemParamMap items; + private ItemParamMap resources; + // Database persistent data private IntSet extraSkins; private IntSet headIcons; @@ -47,13 +52,9 @@ public class Inventory extends PlayerManager implements GameDatabaseObject { private ItemParamMap shopBuyCount; private String2IntMap mallBuyCount; - // Items/resources - private transient Int2ObjectMap resources; - private transient Int2ObjectMap items; - + @Deprecated public Inventory() { - this.resources = new Int2ObjectOpenHashMap<>(); - this.items = new Int2ObjectOpenHashMap<>(); + // Morphia only } public Inventory(Player player) { @@ -62,6 +63,9 @@ public class Inventory extends PlayerManager implements GameDatabaseObject { this.uid = player.getUid(); // Setup + this.resources = new ItemParamMap(); + this.items = new ItemParamMap(); + this.extraSkins = new IntOpenHashSet(); this.headIcons = new IntOpenHashSet(); this.titles = new IntOpenHashSet(); @@ -266,15 +270,13 @@ public class Inventory extends PlayerManager implements GameDatabaseObject { // Resources public synchronized int getResourceCount(int id) { - var res = this.resources.get(id); - return res != null ? res.getCount() : 0; + return this.resources.get(id); } // Items public synchronized int getItemCount(int id) { - var item = this.getItems().get(id); - return item != null ? item.getCount() : 0; + return this.getItems().get(id); } // Add/Remove items @@ -306,35 +308,37 @@ public class Inventory extends PlayerManager implements GameDatabaseObject { // Add item switch (data.getItemType()) { case Res -> { - var res = this.resources.get(id); + int oldAmount = this.resources.get(id); + int newAmount = oldAmount; int diff = 0; if (amount > 0) { // Add resource - if (res == null) { - res = new GameResource(this.getPlayer(), id, amount); - this.resources.put(res.getResourceId(), res); - - diff = amount; - } else { - diff = res.add(amount); - } + newAmount = Utils.safeAdd(oldAmount, amount); + diff = newAmount - oldAmount; - res.save(); + // Set + this.resources.put(id, newAmount); } else { // Remove resource - if (res == null) { - break; - } + newAmount = Utils.safeSubtract(oldAmount, Math.abs(amount)); + diff = newAmount - oldAmount; - diff = res.add(amount); - res.save(); - - if (res.getCount() < 0) { + // Set + if (newAmount > 0) { + this.resources.put(id, newAmount); + } else { this.resources.remove(id); } } + // Update in database + if (newAmount > 0) { + Nebula.getGameDatabase().update(this, this.getPlayerUid(), "resources." + id, newAmount); + } else { + Nebula.getGameDatabase().updateUnset(this, this.getPlayerUid(), "resources." + id); + } + if (diff != 0) { var proto = Res.newInstance() .setTid(id) @@ -365,35 +369,37 @@ public class Inventory extends PlayerManager implements GameDatabaseObject { } // Get item - var item = this.items.get(id); + int oldAmount = this.items.get(id); + int newAmount = oldAmount; int diff = 0; if (amount > 0) { - // Add item - if (item == null) { - item = new GameItem(this.getPlayer(), id, amount); - this.items.put(item.getItemId(), item); - - diff = amount; - } else { - diff = item.add(amount); - } + // Add resource + newAmount = Utils.safeAdd(oldAmount, amount); + diff = newAmount - oldAmount; - item.save(); + // Set + this.items.put(id, newAmount); } else { // Remove resource - if (item == null) { - break; - } + newAmount = Utils.safeSubtract(oldAmount, Math.abs(amount)); + diff = newAmount - oldAmount; - diff = item.add(amount); - item.save(); - - if (item.getCount() < 0) { - this.resources.remove(id); + // Set + if (newAmount > 0) { + this.items.put(id, newAmount); + } else { + this.items.remove(id); } } + // Update in database + if (newAmount > 0) { + Nebula.getGameDatabase().update(this, this.getPlayerUid(), "items." + id, newAmount); + } else { + Nebula.getGameDatabase().updateUnset(this, this.getPlayerUid(), "items." + id); + } + if (diff != 0) { var proto = Item.newInstance() .setTid(id) @@ -893,25 +899,55 @@ public class Inventory extends PlayerManager implements GameDatabaseObject { // Database - public void loadFromDatabase() { + @SuppressWarnings("deprecation") + public void migrateFromDatabase() { var db = Nebula.getGameDatabase(); + boolean save = false; - db.getObjects(GameItem.class, "playerUid", getPlayerUid()).forEach(item -> { - // Get data - var data = GameData.getItemDataTable().get(item.getItemId()); - if (data == null) return; + // Check if we need to handle inventory migration + if (this.items == null) { + this.items = new ItemParamMap(); - // Add - this.items.put(item.getItemId(), item); - }); + // Get inventory items from database + db.getObjects(GameItem.class, "playerUid", getPlayerUid()).forEach(item -> { + // Get data + var data = GameData.getItemDataTable().get(item.getItemId()); + if (data == null) return; + + // Add + this.items.put(item.getItemId(), item.getCount()); + }); + + // Delete all inventory items + db.getDatastore().getCollection(GameItem.class).deleteMany(Filters.eq("playerUid", uid)); + + // Set to save inventory to database + save = true; + } - db.getObjects(GameResource.class, "playerUid", getPlayerUid()).forEach(res -> { - // Get data - var data = GameData.getItemDataTable().get(res.getResourceId()); - if (data == null) return; + if (this.resources == null) { + this.resources = new ItemParamMap(); - // Add - this.resources.put(res.getResourceId(), res); - }); + // Get inventory resources from database + db.getObjects(GameResource.class, "playerUid", getPlayerUid()).forEach(res -> { + // Get data + var data = GameData.getItemDataTable().get(res.getResourceId()); + if (data == null) return; + + // Add + this.resources.put(res.getResourceId(), res.getCount()); + }); + + // Delete all inventory resources + db.getDatastore().getCollection(GameResource.class).deleteMany(Filters.eq("playerUid", uid)); + + // Set to save inventory to database + save = true; + } + + // Update in database + if (save) { + this.save(); + } } } diff --git a/src/main/java/emu/nebula/game/player/Player.java b/src/main/java/emu/nebula/game/player/Player.java index 4529af8..e09541b 100644 --- a/src/main/java/emu/nebula/game/player/Player.java +++ b/src/main/java/emu/nebula/game/player/Player.java @@ -43,8 +43,10 @@ import emu.nebula.proto.Public.CharShow; import emu.nebula.proto.Public.Energy; import emu.nebula.proto.Public.Friend; import emu.nebula.proto.Public.HonorInfo; +import emu.nebula.proto.Public.Item; import emu.nebula.proto.Public.NewbieInfo; import emu.nebula.proto.Public.QuestType; +import emu.nebula.proto.Public.Res; import emu.nebula.proto.Public.WorldClass; import emu.nebula.proto.Public.WorldClassRewardState; import emu.nebula.util.Utils; @@ -789,7 +791,7 @@ public class Player implements GameDatabaseObject { if (this.inventory == null) { this.inventory = this.loadManagerFromDatabase(Inventory.class); } - this.getInventory().loadFromDatabase(); + this.getInventory().migrateFromDatabase(); // Load referenced classes from the database this.formations = this.loadManagerFromDatabase(FormationManager.class); @@ -952,12 +954,20 @@ public class Player implements GameDatabaseObject { proto.addDiscs(disc.toProto()); } - for (var item : getInventory().getItems().values()) { - proto.addItems(item.toProto()); + for (var item : getInventory().getItems().int2IntEntrySet()) { + var info = Item.newInstance() + .setTid(item.getIntKey()) + .setQty(item.getIntValue()); + + proto.addItems(info); } - for (var res : getInventory().getResources().values()) { - proto.addRes(res.toProto()); + for (var res : getInventory().getResources().int2IntEntrySet()) { + var info = Res.newInstance() + .setTid(res.getIntKey()) + .setQty(res.getIntValue()); + + proto.addRes(info); } // Formations diff --git a/src/main/java/emu/nebula/game/player/PlayerModule.java b/src/main/java/emu/nebula/game/player/PlayerModule.java index a404fd5..c4ce6c6 100644 --- a/src/main/java/emu/nebula/game/player/PlayerModule.java +++ b/src/main/java/emu/nebula/game/player/PlayerModule.java @@ -147,6 +147,7 @@ public class PlayerModule extends GameContextModule { * @param uid * @return */ + @SuppressWarnings("deprecation") public synchronized boolean deletePlayer(int uid) { // Make sure player is not online when we are deleting the player Player player = this.getCachedPlayerByUid(uid);