Improved pet handling and fixed some bugs

Players with merged pets will have the merge resolved next time they are dropped
This commit is contained in:
Player Name
2023-12-29 01:09:55 +00:00
committed by Ryan
parent ee88bfe3f0
commit 279cfb8b42
6 changed files with 74 additions and 57 deletions
@@ -1,5 +1,6 @@
import content.global.skill.summoning.familiar.BurdenBeast import content.global.skill.summoning.familiar.BurdenBeast
import content.global.skill.summoning.familiar.FamiliarSpecial import content.global.skill.summoning.familiar.FamiliarSpecial
import content.global.skill.summoning.pet.Pet
import core.api.sendMessage import core.api.sendMessage
import core.game.interaction.InterfaceListener import core.game.interaction.InterfaceListener
@@ -8,23 +9,23 @@ class SummoningTabListener : InterfaceListener {
on(662) { player, _, opcode, buttonID, _, _ -> on(662) { player, _, opcode, buttonID, _, _ ->
when(buttonID) { when(buttonID) {
51 -> { 51 -> {
if (player.getFamiliarManager().hasFamiliar()) { if (player.familiarManager.hasFamiliar()) {
player.getFamiliarManager().getFamiliar().call() player.familiarManager.familiar.call()
} else { } else {
player.getPacketDispatch().sendMessage("You don't have a follower.") player.getPacketDispatch().sendMessage("You don't have a follower.")
} }
} }
67 -> { 67 -> {
if (player.getFamiliarManager().hasFamiliar()) { if (player.familiarManager.hasFamiliar()) {
if (player.familiarManager.familiar.isInvisible || !player.familiarManager.familiar.location.withinDistance(player.location)) { if (player.familiarManager.familiar.isInvisible || !player.familiarManager.familiar.location.withinDistance(player.location)) {
sendMessage(player, "Your familiar is too far away!") sendMessage(player, "Your familiar is too far away!")
return@on true return@on true
} }
if (!player.getFamiliarManager().getFamiliar().isBurdenBeast()) { if (!player.familiarManager.familiar.isBurdenBeast()) {
player.getPacketDispatch().sendMessage("Your familiar is not a beast of burden.") player.getPacketDispatch().sendMessage("Your familiar is not a beast of burden.")
return@on true return@on true
} }
val beast = player.getFamiliarManager().getFamiliar() as BurdenBeast val beast = player.familiarManager.familiar as BurdenBeast
if (beast.getContainer().isEmpty()) { if (beast.getContainer().isEmpty()) {
player.getPacketDispatch().sendMessage("Your familiar is not carrying any items.") player.getPacketDispatch().sendMessage("Your familiar is not carrying any items.")
return@on true return@on true
@@ -35,21 +36,25 @@ class SummoningTabListener : InterfaceListener {
player.getPacketDispatch().sendMessage("You don't have a follower.") player.getPacketDispatch().sendMessage("You don't have a follower.")
} }
53 -> { 53 -> {
if (player.getFamiliarManager().hasFamiliar()) { if (player.familiarManager.hasFamiliar()) {
if(opcode == 155) { if(opcode == 155) {
// Dismiss familiar // Dismiss familiar
player.getDialogueInterpreter().open("dismiss_dial") player.getDialogueInterpreter().open("dismiss_dial")
} else if(opcode == 196) { } else if(opcode == 196) {
// Dismiss now // 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 { } else {
player.getPacketDispatch().sendMessage("You don't have a follower.") player.getPacketDispatch().sendMessage("You don't have a follower.")
} }
} }
else -> { else -> {
if (player.getFamiliarManager().hasFamiliar()) { if (player.familiarManager.hasFamiliar()) {
player.getFamiliarManager().getFamiliar().executeSpecialMove(FamiliarSpecial(player)) player.familiarManager.familiar.executeSpecialMove(FamiliarSpecial(player))
} else { } else {
player.getPacketDispatch().sendMessage("You don't have a follower.") player.getPacketDispatch().sendMessage("You don't have a follower.")
} }
@@ -54,10 +54,12 @@ public final class DismissDialoguePlugin extends DialoguePlugin {
case 1: case 1:
if (player.getFamiliarManager().getFamiliar() instanceof Pet) { if (player.getFamiliarManager().getFamiliar() instanceof Pet) {
interpreter.sendDialogues(player, null, "Run along; I'm setting you free."); interpreter.sendDialogues(player, null, "Run along; I'm setting you free.");
Pet pet = (Pet) player.getFamiliarManager().getFamiliar();
player.getFamiliarManager().removeDetails(pet.getItemIdHash());
} else { } else {
end(); end();
} }
player.getFamiliarManager().dismiss(false); player.getFamiliarManager().dismiss();
stage = 1; stage = 1;
break; break;
case 2: case 2:
@@ -62,11 +62,6 @@ public final class FamiliarManager {
*/ */
private boolean hasPouch; private boolean hasPouch;
/**
* The list of insured pets.
*/
private List<Pets> insuredPets = new ArrayList<>(20);
/** /**
* Constructs a new {@code FamiliarManager} {@code Object}. * Constructs a new {@code FamiliarManager} {@code Object}.
* @param player The player. * @param player The player.
@@ -81,7 +76,7 @@ public final class FamiliarManager {
currentPet = Integer.parseInt(familiarData.get("currentPet").toString()); currentPet = Integer.parseInt(familiarData.get("currentPet").toString());
} }
JSONArray petDetails = (JSONArray) familiarData.get("petDetails"); 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); JSONObject detail = (JSONObject) petDetails.get(i);
PetDetails details = new PetDetails(0); PetDetails details = new PetDetails(0);
details.updateHunger(Double.parseDouble(detail.get("hunger").toString())); details.updateHunger(Double.parseDouble(detail.get("hunger").toString()));
@@ -258,18 +253,34 @@ public final class FamiliarManager {
return false; return false;
} }
PetDetails details = petDetails.get(itemIdHash); // If this pet does not have an individual ID yet, we need to find it an available one.
if (details == null) { //init new pet // 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.
details = new PetDetails(pets.getGrowthRate() == 0.0 ? 100.0 : 0.0); ArrayList<Integer> taken = new ArrayList<Integer>();
// Find an available individual slot Container[] searchSpace = {player.getInventory(), player.getBankPrimary(), player.getBankSecondary()};
ArrayList<Integer> taken = new ArrayList<Integer>(); for (int checkId = pets.getBabyItemId(); checkId != -1; checkId = pets.getNextStageItemId(checkId)) {
Container[] searchSpace = {player.getInventory(), player.getBankPrimary(), player.getBankSecondary()}; Item check = new Item(checkId, 1);
for (Container container : searchSpace) { for (Container container : searchSpace) {
for (Item i : container.getAll(item)) { for (Item i : container.getAll(check)) {
taken.add(i.getCharge()); 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++) {} for (individual = 0; taken.contains(individual) && individual < 0xFFFF; individual++) {}
details.setIndividual(individual); details.setIndividual(individual);
item.setCharge(individual); item.setCharge(individual);
@@ -281,7 +292,10 @@ public final class FamiliarManager {
familiar = new Pet(player, details, itemId, npcId); familiar = new Pet(player, details, itemId, npcId);
if (deleteItem) { if (deleteItem) {
player.animate(new Animation(827)); 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) { if (morph) {
morphFamiliar(location); morphFamiliar(location);
@@ -352,6 +366,7 @@ public final class FamiliarManager {
Item petItem = new Item(pet.getItemId()); Item petItem = new Item(pet.getItemId());
petItem.setCharge(details.getIndividual()); petItem.setCharge(details.getIndividual());
if (player.getInventory().add(petItem)) { if (player.getInventory().add(petItem)) {
petDetails.put(pet.getItemIdHash(),details);
player.animate(Animation.create(827)); player.animate(Animation.create(827));
player.getFamiliarManager().dismiss(); player.getFamiliarManager().dismiss();
} }
@@ -398,24 +413,13 @@ public final class FamiliarManager {
/** /**
* Dismisses the familiar. * Dismisses the familiar.
* @param saveDetails the details of a pet.
*/ */
public void dismiss(boolean saveDetails) { public void dismiss() {
if (hasPet() && !saveDetails) {
removeDetails(((Pet) familiar).getItemIdHash());
}
if (hasFamiliar()) { if (hasFamiliar()) {
familiar.dismiss(); familiar.dismiss();
} }
} }
/**
* Dismisses the familiar.
*/
public void dismiss() {
dismiss(true);
}
/** /**
* Removes the details for this pet. * Removes the details for this pet.
* @param itemIdHash The item id hash of the pet. * @param itemIdHash The item id hash of the pet.
@@ -86,7 +86,8 @@ public final class Pet extends Familiar {
hasWarned = 2; hasWarned = 2;
} }
if (hunger >= 100.0 && growthRate != 0 && pet.getFood().length != 0) { 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); owner.getFamiliarManager().setFamiliar(null);
setVarp(owner, 1175, 0); setVarp(owner, 1175, 0);
owner.sendMessage("<col=ff0000>Your pet has run away.</col>"); owner.sendMessage("<col=ff0000>Your pet has run away.</col>");
@@ -125,14 +126,15 @@ public final class Pet extends Familiar {
// then this pet is already overgrown // then this pet is already overgrown
return; return;
} }
owner.getFamiliarManager().removeDetails(this.getItemIdHash());
owner.getFamiliarManager().dismiss();
owner.getPacketDispatch().sendMessage("<col=ff0000>Your pet has grown larger.</col>");
int npcId = pet.getNpcId(newItemId); int npcId = pet.getNpcId(newItemId);
details.updateGrowth(-100.0); details.updateGrowth(-100.0);
Pet newPet = new Pet(owner, details, newItemId, npcId); Pet newPet = new Pet(owner, details, newItemId, npcId);
newPet.growthRate = growthRate; newPet.growthRate = growthRate;
newPet.hasWarned = hasWarned; newPet.hasWarned = hasWarned;
owner.getFamiliarManager().dismiss(false);
owner.getFamiliarManager().setFamiliar(newPet); owner.getFamiliarManager().setFamiliar(newPet);
owner.getPacketDispatch().sendMessage("<col=ff0000>Your pet has grown larger.</col>");
owner.getFamiliarManager().spawnFamiliar(); owner.getFamiliarManager().spawnFamiliar();
} }
@@ -151,22 +153,6 @@ public final class Pet extends Familiar {
return false; 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. * Gets the itemId.
* @return The itemId. * @return The itemId.
@@ -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) { for (Container container : searchSpace) {
if (container.containsAtLeastOneItem(kittens)) { if (container.containsAtLeastOneItem(kittens)) {
has = true; has = true;
@@ -848,6 +848,26 @@ public class Container {
return -1; 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. * Gets the item instance.
* *