diff --git a/Server/src/main/content/global/skill/summoning/SummoningTabListener.kt b/Server/src/main/content/global/skill/summoning/SummoningTabListener.kt index 8d338a864..2a8debf26 100644 --- a/Server/src/main/content/global/skill/summoning/SummoningTabListener.kt +++ b/Server/src/main/content/global/skill/summoning/SummoningTabListener.kt @@ -1,5 +1,6 @@ import content.global.skill.summoning.familiar.BurdenBeast import content.global.skill.summoning.familiar.FamiliarSpecial +import content.global.skill.summoning.pet.Pet import core.api.sendMessage import core.game.interaction.InterfaceListener @@ -8,23 +9,23 @@ class SummoningTabListener : InterfaceListener { on(662) { player, _, opcode, buttonID, _, _ -> when(buttonID) { 51 -> { - if (player.getFamiliarManager().hasFamiliar()) { - player.getFamiliarManager().getFamiliar().call() + if (player.familiarManager.hasFamiliar()) { + player.familiarManager.familiar.call() } else { player.getPacketDispatch().sendMessage("You don't have a follower.") } } 67 -> { - if (player.getFamiliarManager().hasFamiliar()) { + if (player.familiarManager.hasFamiliar()) { if (player.familiarManager.familiar.isInvisible || !player.familiarManager.familiar.location.withinDistance(player.location)) { sendMessage(player, "Your familiar is too far away!") return@on true } - if (!player.getFamiliarManager().getFamiliar().isBurdenBeast()) { + if (!player.familiarManager.familiar.isBurdenBeast()) { player.getPacketDispatch().sendMessage("Your familiar is not a beast of burden.") return@on true } - val beast = player.getFamiliarManager().getFamiliar() as BurdenBeast + val beast = player.familiarManager.familiar as BurdenBeast if (beast.getContainer().isEmpty()) { player.getPacketDispatch().sendMessage("Your familiar is not carrying any items.") return@on true @@ -35,21 +36,25 @@ class SummoningTabListener : InterfaceListener { player.getPacketDispatch().sendMessage("You don't have a follower.") } 53 -> { - if (player.getFamiliarManager().hasFamiliar()) { + if (player.familiarManager.hasFamiliar()) { if(opcode == 155) { // Dismiss familiar player.getDialogueInterpreter().open("dismiss_dial") } else if(opcode == 196) { // Dismiss now - player.getFamiliarManager().dismiss(false) + if (player.getFamiliarManager().getFamiliar() is Pet) { + val pet = player.familiarManager.familiar as Pet + player.familiarManager.removeDetails(pet.getItemIdHash()) + } + player.familiarManager.dismiss() } } else { player.getPacketDispatch().sendMessage("You don't have a follower.") } } else -> { - if (player.getFamiliarManager().hasFamiliar()) { - player.getFamiliarManager().getFamiliar().executeSpecialMove(FamiliarSpecial(player)) + if (player.familiarManager.hasFamiliar()) { + player.familiarManager.familiar.executeSpecialMove(FamiliarSpecial(player)) } else { player.getPacketDispatch().sendMessage("You don't have a follower.") } diff --git a/Server/src/main/content/global/skill/summoning/familiar/DismissDialoguePlugin.java b/Server/src/main/content/global/skill/summoning/familiar/DismissDialoguePlugin.java index 641f95121..ff053a5da 100644 --- a/Server/src/main/content/global/skill/summoning/familiar/DismissDialoguePlugin.java +++ b/Server/src/main/content/global/skill/summoning/familiar/DismissDialoguePlugin.java @@ -54,10 +54,12 @@ public final class DismissDialoguePlugin extends DialoguePlugin { case 1: if (player.getFamiliarManager().getFamiliar() instanceof Pet) { interpreter.sendDialogues(player, null, "Run along; I'm setting you free."); + Pet pet = (Pet) player.getFamiliarManager().getFamiliar(); + player.getFamiliarManager().removeDetails(pet.getItemIdHash()); } else { end(); } - player.getFamiliarManager().dismiss(false); + player.getFamiliarManager().dismiss(); stage = 1; break; case 2: diff --git a/Server/src/main/content/global/skill/summoning/familiar/FamiliarManager.java b/Server/src/main/content/global/skill/summoning/familiar/FamiliarManager.java index ce2a43d75..4cd7dc5e8 100644 --- a/Server/src/main/content/global/skill/summoning/familiar/FamiliarManager.java +++ b/Server/src/main/content/global/skill/summoning/familiar/FamiliarManager.java @@ -62,11 +62,6 @@ public final class FamiliarManager { */ private boolean hasPouch; - /** - * The list of insured pets. - */ - private List insuredPets = new ArrayList<>(20); - /** * Constructs a new {@code FamiliarManager} {@code Object}. * @param player The player. @@ -81,7 +76,7 @@ public final class FamiliarManager { currentPet = Integer.parseInt(familiarData.get("currentPet").toString()); } JSONArray petDetails = (JSONArray) familiarData.get("petDetails"); - for (int i = 0 ; i < petDetails.size(); i++) { + for (int i = 0; i < petDetails.size(); i++) { JSONObject detail = (JSONObject) petDetails.get(i); PetDetails details = new PetDetails(0); details.updateHunger(Double.parseDouble(detail.get("hunger").toString())); @@ -258,18 +253,34 @@ public final class FamiliarManager { return false; } - PetDetails details = petDetails.get(itemIdHash); - if (details == null) { //init new pet - details = new PetDetails(pets.getGrowthRate() == 0.0 ? 100.0 : 0.0); - // Find an available individual slot - ArrayList taken = new ArrayList(); - Container[] searchSpace = {player.getInventory(), player.getBankPrimary(), player.getBankSecondary()}; + // If this pet does not have an individual ID yet, we need to find it an available one. + // If it does, we need to verify that this ID is not already used for a different pet. This is needed to correct a historical bug that allowed multiple pets to be assigned the same individual ID (the historical code only checked the *current* stage item ID, failing to realize that we also need to account for *future* stage item IDs, in case the current pet grows up, resulting in a clash when it did). Saves affected by that bug will have multiple copies of the same item pointing to the same pet, which we have an opportunity to rectify now. + ArrayList taken = new ArrayList(); + Container[] searchSpace = {player.getInventory(), player.getBankPrimary(), player.getBankSecondary()}; + for (int checkId = pets.getBabyItemId(); checkId != -1; checkId = pets.getNextStageItemId(checkId)) { + Item check = new Item(checkId, 1); for (Container container : searchSpace) { - for (Item i : container.getAll(item)) { + for (Item i : container.getAll(check)) { taken.add(i.getCharge()); } } - int individual; + } + PetDetails details = petDetails.get(itemIdHash); + int individual = item.getCharge(); + if (details != null) { //we have this pet on file, but we need to check that it wasn't affected by the historical bug mentioned above + details.setIndividual(individual); + int count = 0; + for (int i : taken) { + if (i == individual) { + count++; + } + } + if (count > 1) { //this pet is sadly conjoined with another individual of its kind; untangle it by initializing it anew (which is what should have happened in the first place, save the minor detail of hunger propagation from the previous stage, which we no longer have any record of) + details = null; + } + } + if (details == null) { //init new pet + details = new PetDetails(pets.getGrowthRate() == 0.0 ? 100.0 : 0.0); for (individual = 0; taken.contains(individual) && individual < 0xFFFF; individual++) {} details.setIndividual(individual); item.setCharge(individual); @@ -281,7 +292,10 @@ public final class FamiliarManager { familiar = new Pet(player, details, itemId, npcId); if (deleteItem) { player.animate(new Animation(827)); - player.getInventory().remove(item); + // We cannot use player().getInventory().remove(item), because that will remove the first pet item it sees, rather than the specific one (with the specific charge value) the player clicked. + // Instead, find the specific item the player dropped by slot, and remove that specific one. + int slot = player.getInventory().getSlotHash(item); + player.getInventory().remove(item, slot, true); } if (morph) { morphFamiliar(location); @@ -352,6 +366,7 @@ public final class FamiliarManager { Item petItem = new Item(pet.getItemId()); petItem.setCharge(details.getIndividual()); if (player.getInventory().add(petItem)) { + petDetails.put(pet.getItemIdHash(),details); player.animate(Animation.create(827)); player.getFamiliarManager().dismiss(); } @@ -398,24 +413,13 @@ public final class FamiliarManager { /** * Dismisses the familiar. - * @param saveDetails the details of a pet. */ - public void dismiss(boolean saveDetails) { - if (hasPet() && !saveDetails) { - removeDetails(((Pet) familiar).getItemIdHash()); - } + public void dismiss() { if (hasFamiliar()) { familiar.dismiss(); } } - /** - * Dismisses the familiar. - */ - public void dismiss() { - dismiss(true); - } - /** * Removes the details for this pet. * @param itemIdHash The item id hash of the pet. diff --git a/Server/src/main/content/global/skill/summoning/pet/Pet.java b/Server/src/main/content/global/skill/summoning/pet/Pet.java index 3222e4ec7..e4812dac1 100644 --- a/Server/src/main/content/global/skill/summoning/pet/Pet.java +++ b/Server/src/main/content/global/skill/summoning/pet/Pet.java @@ -86,7 +86,8 @@ public final class Pet extends Familiar { hasWarned = 2; } if (hunger >= 100.0 && growthRate != 0 && pet.getFood().length != 0) { - owner.getFamiliarManager().dismiss(false); + owner.getFamiliarManager().removeDetails(this.getItemIdHash()); + owner.getFamiliarManager().dismiss(); owner.getFamiliarManager().setFamiliar(null); setVarp(owner, 1175, 0); owner.sendMessage("Your pet has run away."); @@ -125,14 +126,15 @@ public final class Pet extends Familiar { // then this pet is already overgrown return; } + owner.getFamiliarManager().removeDetails(this.getItemIdHash()); + owner.getFamiliarManager().dismiss(); + owner.getPacketDispatch().sendMessage("Your pet has grown larger."); int npcId = pet.getNpcId(newItemId); details.updateGrowth(-100.0); Pet newPet = new Pet(owner, details, newItemId, npcId); newPet.growthRate = growthRate; newPet.hasWarned = hasWarned; - owner.getFamiliarManager().dismiss(false); owner.getFamiliarManager().setFamiliar(newPet); - owner.getPacketDispatch().sendMessage("Your pet has grown larger."); owner.getFamiliarManager().spawnFamiliar(); } @@ -151,22 +153,6 @@ public final class Pet extends Familiar { return false; } - /** - * Gets the growthRate. - * @return The growthRate. - */ - public double getGrowthRate() { - return growthRate; - } - - /** - * Sets the growthRate. - * @param growthRate The growthRate to set. - */ - public void setGrowthRate(double growthRate) { - this.growthRate = growthRate; - } - /** * Gets the itemId. * @return The itemId. diff --git a/Server/src/main/content/region/misthalin/varrock/dialogue/GertrudeDialogue.java b/Server/src/main/content/region/misthalin/varrock/dialogue/GertrudeDialogue.java index ca854e748..dae9474cd 100644 --- a/Server/src/main/content/region/misthalin/varrock/dialogue/GertrudeDialogue.java +++ b/Server/src/main/content/region/misthalin/varrock/dialogue/GertrudeDialogue.java @@ -340,7 +340,7 @@ public final class GertrudeDialogue extends DialoguePlugin { } } } - Container[] searchSpace = {player.getInventory(), player.getBankPrimary(), player.getBankSecondary()}; + Container[] searchSpace = {player.getInventory(), player.getBank()}; for (Container container : searchSpace) { if (container.containsAtLeastOneItem(kittens)) { has = true; diff --git a/Server/src/main/core/game/container/Container.java b/Server/src/main/core/game/container/Container.java index b45f4d8f2..431151cd0 100644 --- a/Server/src/main/core/game/container/Container.java +++ b/Server/src/main/core/game/container/Container.java @@ -848,6 +848,26 @@ public class Container { return -1; } + /** + * Gets the item slot, taking into account the item's whole hash rather than just the ID part. + * + * @param item The item. + * @return The slot of the item in this container. + */ + public int getSlotHash(Item item) { + if (item == null) { + return -1; + } + int idHash = item.getIdHash(); + for (int i = 0; i < items.length; i++) { + Item it = items[i]; + if (it != null && it.getIdHash() == idHash) { + return i; + } + } + return -1; + } + /** * Gets the item instance. *