Rewrote pet back end to use a more authentic system

Pets can now stack
Fixes a bug where a pet could get reset to 0 hunger and growth when dropped
Player save version migration messages are no longer shown
Pets now morph into adults in-place
This commit is contained in:
Player Name
2024-10-06 10:47:36 +00:00
committed by Ryan
parent 6b0f942598
commit 9c202aa47a
19 changed files with 204 additions and 684 deletions
@@ -47,7 +47,7 @@ class CatOnArdougneCivilian: InteractionListener {
override fun defineListeners() { override fun defineListeners() {
onUseWith(IntType.NPC,cats,*civilians){ player, used, _ -> onUseWith(IntType.NPC,cats,*civilians){ player, used, _ ->
sendItemDialogue(player,Items.DEATH_RUNE_560,"You hand over the cat.<br>You are given 100 Death Runes.") sendItemDialogue(player,Items.DEATH_RUNE_560,"You hand over the cat.<br>You are given 100 Death Runes.")
player.familiarManager.removeDetails(used.idHash) player.familiarManager.removeDetails(used.id)
removeItem(player,used,Container.INVENTORY) removeItem(player,used,Container.INVENTORY)
addItem(player,Items.DEATH_RUNE_560,100) addItem(player,Items.DEATH_RUNE_560,100)
return@onUseWith true return@onUseWith true
@@ -44,7 +44,7 @@ class SummoningTabListener : InterfaceListener {
// Dismiss now // Dismiss now
if (player.getFamiliarManager().getFamiliar() is Pet) { if (player.getFamiliarManager().getFamiliar() is Pet) {
val pet = player.familiarManager.familiar as Pet val pet = player.familiarManager.familiar as Pet
player.familiarManager.removeDetails(pet.getItemIdHash()) player.familiarManager.removeDetails(pet.getItemId())
} }
player.familiarManager.dismiss() player.familiarManager.dismiss()
} }
@@ -1,103 +0,0 @@
package content.global.skill.summoning.familiar;
import core.game.dialogue.DialoguePlugin;
import core.game.dialogue.FacialExpression;
import core.game.node.entity.npc.Metamorphosis;
import core.game.node.entity.npc.NPC;
import core.game.node.entity.player.Player;
import core.plugin.Initializable;
import core.tools.RandomFunction;
/**
* Handles the baby chinchompa pet.
* @author Empathy
*
*/
@Initializable
public class BabyChinchompaNPC extends Metamorphosis {
/**
* The chinchompa ids.
*/
private static final int[] CHINCHOMPA_IDS = new int[] { 8643, 8644, 8657, 8658 };
/**
* Constructs a new {@code BabyChinchompaNPC} object.
*/
public BabyChinchompaNPC() {
super(CHINCHOMPA_IDS);
}
@Override
public DialoguePlugin getDialoguePlugin() {
return new BabyChinchompaDialogue();
}
@Override
public int getRandomNpcId() {
int i = RandomFunction.getRandom(getIds().length - 1);
if (getIds()[i] == 8658) {
int x = RandomFunction.getRandom(30);
if (x == 1) {
return getIds()[i];
} else {
return getIds()[i-1];
}
}
return getIds()[i];
}
/**
* Handles the BabyChinchompa Dialogue.
* @author Empathy
*
*/
public final class BabyChinchompaDialogue extends DialoguePlugin {
/**
* Constructs a new {@code BabyChinchompaDialogue} {@code Object}.
*/
public BabyChinchompaDialogue() {
/**
* empty.
*/
}
/**
* Constructs a new {@code BabyChinchompaDialogue} {@code Object}.
*
* @param player the player.
*/
public BabyChinchompaDialogue(Player player) {
super(player);
}
@Override
public DialoguePlugin newInstance(Player player) {
return new BabyChinchompaDialogue(player);
}
@Override
public boolean open(Object... args) {
npc = (NPC) args[0];
interpreter.sendDialogues(npc, FacialExpression.OLD_NORMAL, npc.getId() != 8658 ? "Squeak! Squeak!" : "Squeaka! Squeaka!");
stage = 0;
return true;
}
@Override
public boolean handle(int interfaceId, int buttonId) {
switch (stage) {
case 0:
end();
break;
}
return true;
}
@Override
public int[] getIds() {
return new int[] { 8643, 8644, 8657, 8658 };
}
}
}
@@ -55,7 +55,7 @@ public final class DismissDialoguePlugin extends DialoguePlugin {
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(); Pet pet = (Pet) player.getFamiliarManager().getFamiliar();
player.getFamiliarManager().removeDetails(pet.getItemIdHash()); player.getFamiliarManager().removeDetails(pet.getItemId());
} else { } else {
end(); end();
} }
@@ -4,7 +4,6 @@ import content.global.skill.summoning.pet.Pet;
import content.global.skill.summoning.pet.Pets; import content.global.skill.summoning.pet.Pets;
import core.cache.def.impl.ItemDefinition; import core.cache.def.impl.ItemDefinition;
import core.game.component.Component; import core.game.component.Component;
import core.game.container.Container;
import org.json.simple.JSONArray; import org.json.simple.JSONArray;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;
import core.game.node.entity.skill.Skills; import core.game.node.entity.skill.Skills;
@@ -20,7 +19,6 @@ import core.game.world.update.flag.context.Animation;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import static core.api.ContentAPIKt.*; import static core.api.ContentAPIKt.*;
@@ -38,9 +36,9 @@ public final class FamiliarManager {
private static final Map<Integer, Familiar> FAMILIARS = new HashMap<>(); private static final Map<Integer, Familiar> FAMILIARS = new HashMap<>();
/** /**
* The pet details mapping, sorted by item id. * The pet details mapping.
*/ */
private final Map<Integer, PetDetails> petDetails = new HashMap<Integer, PetDetails>(); private final Map<Integer, ArrayList<PetDetails>> petDetails = new HashMap<>();
/** /**
* The player. * The player.
@@ -70,23 +68,33 @@ public final class FamiliarManager {
this.player = player; this.player = player;
} }
public final void parse(JSONObject familiarData) { public void parse(JSONObject familiarData) {
for (Pets pet : Pets.values()) {
for (int id : new int[]{pet.getBabyItemId(), pet.getGrownItemId(), pet.getOvergrownItemId()}) {
if (id != -1) {
petDetails.put(id, new ArrayList<PetDetails>());
}
}
}
int currentPet = -1; int currentPet = -1;
if (familiarData.containsKey("currentPet")) { if (familiarData.containsKey("currentPet")) {
currentPet = Integer.parseInt(familiarData.get("currentPet").toString()); currentPet = Integer.parseInt(familiarData.get("currentPet").toString());
} }
if (player.version < 2) { //migrate the v1 format
JSONArray petDetails = (JSONArray) familiarData.get("petDetails"); JSONArray petDetails = (JSONArray) familiarData.get("petDetails");
for (int i = 0; i < petDetails.size(); i++) { for (Object petDetail : petDetails) {
JSONObject detail = (JSONObject) petDetails.get(i); JSONObject detail = (JSONObject) petDetail;
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()));
details.updateGrowth(Double.parseDouble(detail.get("growth").toString())); details.updateGrowth(Double.parseDouble(detail.get("growth").toString()));
int itemId;
int itemIdHash = Integer.parseInt(detail.get("petId").toString()); int itemIdHash = Integer.parseInt(detail.get("petId").toString());
// The below is for migrating legacy saves, which stored baby item IDs + growth stages // The below is for migrating the v0 format, which stored baby item IDs + growth stages
if (detail.containsKey("stage")) { if (detail.containsKey("stage")) {
// The "itemIdHash" is actually the baby item ID. The "stage" gives the actual pet stage we want. // The itemIdHash is actually the baby item ID. The "stage" gives the actual pet stage we want.
int babyItemId = itemIdHash; int babyItemId = itemIdHash;
int itemId = babyItemId; itemId = babyItemId;
int stage = Integer.parseInt(detail.get("stage").toString()); int stage = Integer.parseInt(detail.get("stage").toString());
if (stage > 0) { if (stage > 0) {
Pets pets = Pets.forId(babyItemId); Pets pets = Pets.forId(babyItemId);
@@ -95,25 +103,34 @@ public final class FamiliarManager {
itemId = pets.getNextStageItemId(itemId); itemId = pets.getNextStageItemId(itemId);
} }
} }
Item item = new Item(itemId); } else {
item.setCharge(1000); //this is the default value that will correspond to the player's item itemId = itemIdHash >> 16 & 0xFFFF; //in the legacy v1 format, was hash rather than item id
itemIdHash = item.getIdHash(); }
if (currentPet != -1 && currentPet == babyItemId) { this.petDetails.get(itemId).add(details);
currentPet = itemIdHash; }
if (currentPet > 65536) {
currentPet = currentPet >> 16 & 0xFFFF; //in the legacy v1 format, was hash rather than item id
}
} else {
JSONObject petDetails = (JSONObject) familiarData.get("petDetails");
for (Object key : petDetails.keySet()) {
int itemId = Integer.parseInt(key.toString());
this.petDetails.put(itemId, new ArrayList<>());
JSONArray values = (JSONArray) petDetails.get(key.toString());
for (Object petDetail : values) {
JSONObject detail = (JSONObject) petDetail;
PetDetails details = new PetDetails(0);
details.updateHunger(Double.parseDouble(detail.get("hunger").toString()));
details.updateGrowth(Double.parseDouble(detail.get("growth").toString()));
this.petDetails.get(itemId).add(details);
} }
} }
this.petDetails.put(itemIdHash, details);
} }
if (currentPet != -1) { if (currentPet != -1) {
PetDetails details = this.petDetails.get(currentPet); int last = this.petDetails.get(currentPet).size() - 1;
int itemId = currentPet >> 16 & 0xFFFF; PetDetails details = this.petDetails.get(currentPet).get(last);
Pets pets = Pets.forId(itemId); Pets pets = Pets.forId(currentPet);
if (details == null) { familiar = new Pet(player, details, currentPet, pets.getNpcId(currentPet));
details = new PetDetails(pets.getGrowthRate() == 0.0 ? 100.0 : 0.0);
this.petDetails.put(currentPet, details);
}
familiar = new Pet(player, details, itemId, pets.getNpcId(itemId));
} else if (familiarData.containsKey("familiar")) { } else if (familiarData.containsKey("familiar")) {
JSONObject currentFamiliar = (JSONObject) familiarData.get("familiar"); JSONObject currentFamiliar = (JSONObject) familiarData.get("familiar");
int familiarId = Integer.parseInt( currentFamiliar.get("originalId").toString()); int familiarId = Integer.parseInt( currentFamiliar.get("originalId").toString());
@@ -217,11 +234,10 @@ public final class FamiliarManager {
* @param deleteItem the item. * @param deleteItem the item.
* @param location the location. * @param location the location.
*/ */
public void morphPet(final Item item, boolean deleteItem, Location location) { public void morphPet(final Item item, boolean deleteItem, Location location, double hunger, double growth) {
if (hasFamiliar()) { int hasWarned = ((Pet) familiar).getHasWarned();
familiar.dismiss(); familiar.dismiss();
} summonPet(item, deleteItem, true, location, hasWarned, hunger, growth);
summonPet(item, deleteItem, true, location);
} }
/** /**
@@ -230,7 +246,7 @@ public final class FamiliarManager {
* @param deleteItem the item. * @param deleteItem the item.
*/ */
private boolean summonPet(final Item item, boolean deleteItem) { private boolean summonPet(final Item item, boolean deleteItem) {
return summonPet(item, deleteItem, false, null); return summonPet(item, deleteItem, false, null, 0, -1, -1);
} }
/** /**
@@ -238,9 +254,8 @@ public final class FamiliarManager {
* @param item the item. * @param item the item.
* @param morph the pet. * @param morph the pet.
*/ */
private boolean summonPet(final Item item, boolean deleteItem, boolean morph, Location location) { private boolean summonPet(final Item item, boolean deleteItem, boolean morph, Location location, int hasWarned, double hunger, double growth) {
final int itemId = item.getId(); final int itemId = item.getId();
int itemIdHash = item.getIdHash();
if (itemId > 8850 && itemId < 8900) { if (itemId > 8850 && itemId < 8900) {
return false; return false;
} }
@@ -252,52 +267,24 @@ public final class FamiliarManager {
player.getDialogueInterpreter().sendDialogue("You need a summoning level of " + pets.getSummoningLevel() + " to summon this."); player.getDialogueInterpreter().sendDialogue("You need a summoning level of " + pets.getSummoningLevel() + " to summon this.");
return false; return false;
} }
int last = this.petDetails.get(itemId).size() - 1;
// If this pet does not have an individual ID yet, we need to find it an available one. if (last < 0) { //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. last = 0;
ArrayList<Integer> taken = new ArrayList<Integer>(); PetDetails details = new PetDetails(pets.getGrowthRate() == 0.0 ? 100.0 : 0.0);
Container[] searchSpace = {player.getInventory(), player.getBankPrimary(), player.getBankSecondary()}; this.petDetails.get(itemId).add(details);
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(check)) {
taken.add(i.getCharge());
}
}
}
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);
// Make a copy of the item to extract what the item's idHash will be when including the individual ID as a "charge" value.
// The copy is necessary since the player's inventory still contains the default-charged item, which we will be removing only later.
Item newItem = item.copy();
newItem.setCharge(individual);
petDetails.put(newItem.getIdHash(), details);
} }
PetDetails details = this.petDetails.get(itemId).get(last);
int npcId = pets.getNpcId(itemId); int npcId = pets.getNpcId(itemId);
if (npcId > 0) { if (npcId > 0) {
familiar = new Pet(player, details, itemId, npcId); familiar = new Pet(player, details, itemId, npcId);
((Pet) familiar).setHasWarned(hasWarned);
if (hunger != -1) ((Pet) familiar).getDetails().setHunger(hunger);
if (growth != -1) ((Pet) familiar).getDetails().setGrowth(growth);
if (deleteItem) { if (deleteItem) {
player.animate(new Animation(827)); player.animate(new Animation(827));
// 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. if (!player.getInventory().remove(item, true)) {
// Instead, find the specific item the player dropped by slot, and remove that specific one. return false;
int slot = player.getInventory().getSlotHash(item); }
player.getInventory().remove(item, slot, true);
} }
if (morph) { if (morph) {
morphFamiliar(location); morphFamiliar(location);
@@ -364,11 +351,7 @@ public final class FamiliarManager {
return; return;
} }
Pet pet = ((Pet) familiar); Pet pet = ((Pet) familiar);
PetDetails details = pet.getDetails(); if (player.getInventory().add(new Item(pet.getItemId()))) {
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.animate(Animation.create(827));
player.getFamiliarManager().dismiss(); player.getFamiliarManager().dismiss();
} }
@@ -423,11 +406,23 @@ public final class FamiliarManager {
} }
/** /**
* Removes the details for this pet. * Adds pet details for a new pet to that pet's stack.
* @param itemIdHash The item id hash of the pet. * @param itemId The item id of the pet.
* @param details The new pet details.
*/ */
public void removeDetails(int itemIdHash) { public void addDetails(int itemId, PetDetails details) {
petDetails.remove(itemIdHash); petDetails.get(itemId).add(details);
}
/**
* Removes the details for this pet.
* @param itemId The item id of the pet.
*/
public void removeDetails(int itemId) {
int last = petDetails.get(itemId).size() - 1;
if (last >= 0) {
petDetails.get(itemId).remove(last);
}
} }
/** /**
@@ -521,7 +516,7 @@ public final class FamiliarManager {
} }
public Map<Integer, PetDetails> getPetDetails() { public Map<Integer, ArrayList<PetDetails>> getPetDetails() {
return petDetails; return petDetails;
} }
} }
@@ -1,117 +0,0 @@
package content.global.skill.summoning.familiar;
import core.plugin.Initializable;
import core.game.dialogue.DialoguePlugin;
import core.game.dialogue.FacialExpression;
import core.game.node.entity.npc.Metamorphosis;
import core.game.node.entity.npc.NPC;
import core.game.node.entity.player.Player;
/**
* Handles the kalphite princess pet.
* @author Empathy
*
*/
@Initializable
public class KalphiteNPC extends Metamorphosis {
/**
* The kalphite ids.
*/
private static final int[] KALPHITE_IDS = new int[] { 8602, 8603 };
/**
* Constructs a new {@code KalphiteNPC} object.
*/
public KalphiteNPC() {
super(KALPHITE_IDS);
}
@Override
public DialoguePlugin getDialoguePlugin() {
return new KalphitePrincessDialogue();
}
/**
* Handles the KalphitePrincess dialogue.
* @author Empathy
*
*/
public final class KalphitePrincessDialogue extends DialoguePlugin {
/**
* Constructs a new {@code KalphitePrincessDialogue} {@code Object}.
*/
public KalphitePrincessDialogue() {
/**
* empty.
*/
}
/**
* Constructs a new {@code KalphitePrincessDialogue} {@code Object}.
*
* @param player the player.
*/
public KalphitePrincessDialogue(Player player) {
super(player);
}
@Override
public DialoguePlugin newInstance(Player player) {
return new KalphitePrincessDialogue(player);
}
@Override
public boolean open(Object... args) {
npc = (NPC) args[0];
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "What is it with your kind and potato cactus?");
stage = 0;
return true;
}
@Override
public boolean handle(int interfaceId, int buttonId) {
switch (stage) {
case 0:
interpreter.sendDialogues(npc, FacialExpression.OLD_NORMAL, "Truthfully?");
stage = 1;
break;
case 1:
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Yeah, please.");
stage = 2;
break;
case 2:
interpreter.sendDialogues(npc, FacialExpression.OLD_NORMAL, "Soup. We make a fine soup with it.");
stage = 3;
break;
case 3:
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Kalphites can cook?");
stage = 4;
break;
case 4:
interpreter.sendDialogues(npc, FacialExpression.OLD_NORMAL, "Nah, we just collect it and put it there because we", "know fools like yourself will come down looking for it", "then inevitably be killed by my mother.");
stage = 5;
break;
case 5:
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Evidently not, that's how I got you!");
stage = 6;
break;
case 6:
interpreter.sendDialogues(npc, FacialExpression.OLD_NORMAL, "Touch.");
stage = 7;
break;
case 7:
end();
break;
}
return true;
}
@Override
public int[] getIds() {
return new int[] { 8602, 8603 };
}
}
}
@@ -1,114 +0,0 @@
package content.global.skill.summoning.familiar;
import core.plugin.Initializable;
import core.game.dialogue.DialoguePlugin;
import core.game.dialogue.FacialExpression;
import core.game.node.entity.npc.Metamorphosis;
import core.game.node.entity.npc.NPC;
import core.game.node.entity.player.Player;
/**
* Handles the metamorphosis of the zulrah pet.
* @author Empathy
*
*/
@Initializable
public class SnakelingNPC extends Metamorphosis {
/**
* The snakeling ids.
*/
public static final int[] SNAKELING_IDS = new int[] { 8626, 8627, 8628 };
/**
*
* Constructs a new {@code SnakelingNPC} object.
*/
public SnakelingNPC() {
super(SNAKELING_IDS);
}
@Override
public DialoguePlugin getDialoguePlugin() {
return new PetSnakelingDialogue();
}
/**
* Handles the pet snakeling dialogue.
* @author Empathy
*
*/
public final class PetSnakelingDialogue extends DialoguePlugin {
/**
* Constructs a new {@code PetSnakelingDialogue} {@code Object}.
*/
public PetSnakelingDialogue() {
/**
* empty.
*/
}
/**
* Constructs a new {@code PetSnakelingDialogue} {@code Object}.
*
* @param player the player.
*/
public PetSnakelingDialogue(Player player) {
super(player);
}
@Override
public DialoguePlugin newInstance(Player player) {
return new PetSnakelingDialogue(player);
}
@Override
public boolean open(Object... args) {
npc = (NPC) args[0];
player("Hey little snake!");
stage = 0;
return true;
}
@Override
public boolean handle(int interfaceId, int buttonId) {
switch (stage) {
case 0:
npc(FacialExpression.OLD_NORMAL, "Soon, Zulrah shall establish dominion over this plane.");
stage = 1;
break;
case 1:
player("Wanna play fetch?");
stage = 2;
break;
case 2:
npc(FacialExpression.OLD_NORMAL, "Submit to the almighty Zulrah.");
stage = 3;
break;
case 3:
player("Walkies? Or slidies...?");
stage = 4;
break;
case 4:
npc(FacialExpression.OLD_NORMAL, "Zulrah's wilderness as a God will soon be demonstrated.");
stage = 5;
break;
case 5:
player("I give up...");
stage = 6;
break;
case 6:
end();
break;
}
return true;
}
@Override
public int[] getIds() {
return SNAKELING_IDS;
}
}
}
@@ -1,33 +0,0 @@
package content.global.skill.summoning.familiar;
import core.plugin.Initializable;
import core.game.dialogue.DialoguePlugin;
import core.game.node.entity.npc.Metamorphosis;
/**
* Handles the metamorphosis of Vet'ion Jr.
* @author Empathy
*
*/
@Initializable
public class VetionNPC extends Metamorphosis {
/**
* The Vet'ion Ids.
*/
public static final int[] VETION_IDS = new int[] { 8600, 8654 };
/**
*
* Constructs a new{@code VetionNPC} object.
*/
public VetionNPC() {
super(VETION_IDS);
}
@Override
public DialoguePlugin getDialoguePlugin() {
return null;
}
}
@@ -117,8 +117,8 @@ public final class KittenInteractDialogue extends DialoguePlugin {
player.sendChat("Shoo cat!"); player.sendChat("Shoo cat!");
Pet currentPet = (Pet) player.getFamiliarManager().getFamiliar(); Pet currentPet = (Pet) player.getFamiliarManager().getFamiliar();
player.getFamiliarManager().getFamiliar().sendChat("Miaow!"); player.getFamiliarManager().getFamiliar().sendChat("Miaow!");
player.getFamiliarManager().removeDetails(currentPet.getItemIdHash());
player.getFamiliarManager().getFamiliar().dismiss(); player.getFamiliarManager().getFamiliar().dismiss();
player.getFamiliarManager().removeDetails(currentPet.getItemId());
player.getPacketDispatch().sendMessage("The cat has run away."); player.getPacketDispatch().sendMessage("The cat has run away.");
} }
end(); end();
@@ -30,7 +30,7 @@ public final class Pet extends Familiar {
/** /**
* The growth rate of the pet. * The growth rate of the pet.
*/ */
private double growthRate; private final double growthRate;
/** /**
* The pets type. * The pets type.
@@ -86,8 +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().removeDetails(this.getItemIdHash());
owner.getFamiliarManager().dismiss(); owner.getFamiliarManager().dismiss();
owner.getFamiliarManager().removeDetails(getItemId());
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>");
@@ -126,16 +126,10 @@ 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().removeDetails(getItemId());
owner.getFamiliarManager().dismiss(); owner.getFamiliarManager().addDetails(newItemId, details);
owner.getFamiliarManager().morphPet(new Item(newItemId), false, location, details.getHunger(), 0);
owner.getPacketDispatch().sendMessage("<col=ff0000>Your pet has grown larger.</col>"); owner.getPacketDispatch().sendMessage("<col=ff0000>Your pet has grown larger.</col>");
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().setFamiliar(newPet);
owner.getFamiliarManager().spawnFamiliar();
} }
@Override @Override
@@ -161,16 +155,6 @@ public final class Pet extends Familiar {
return itemId; return itemId;
} }
/**
* Gets the itemId with the individual hashed in.
* @return The itemIdHash.
*/
public int getItemIdHash() {
Item item = new Item(itemId);
item.setCharge(details.getIndividual());
return item.getIdHash();
}
/** /**
* Gets the details. * Gets the details.
* @return The details. * @return The details.
@@ -187,9 +171,36 @@ public final class Pet extends Familiar {
return pet; return pet;
} }
/**
* Gets the hunger level.
*/
public double getHunger() {
return details.getHunger();
}
/**
* Gets the growth level.
*/
public double getGrowth() {
return details.getGrowth();
}
/**
* Gets the hunger warning level.
*/
public int getHasWarned() {
return hasWarned;
}
/**
* Sets the hunger warning level.
*/
public void setHasWarned(int value) {
this.hasWarned = value;
}
@Override @Override
public int[] getIds() { public int[] getIds() {
return new int[] { 761, 762, 763, 764, 765, 766, 3505, 3598, 6969, 7259, 7260, 6964, 7249, 7251, 6960, 7241, 7243, 6962, 7245, 7247, 6966, 7253, 7255, 6958, 7237, 7239, 6915, 7277, 7278, 7279, 7280, 7018, 7019, 7020, 6908, 7313, 7316, 6947, 7293, 7295, 7297, 7299, 6911, 7261, 7263, 7265, 7267, 7269, 6919, 7301, 7303, 7305, 7307, 6949, 6952, 6955, 6913, 7271, 7273, 6945, 7319, 7321, 7323, 7325, 7327, 6922, 6942, 7210, 7212, 7214, 7216, 7218, 7220, 7222, 7224, 7226, 6900, 6902, 6904, 6906, 768, 769, 770, 771, 772, 773, 3504, 6968, 7257, 7258, 6965, 7250, 7252, 6961, 7242, 7244, 6963, 7246, 7248, 6967, 7254, 7256, 6859, 7238, 7240, 6916, 7281, 7282, 7283, 7284, 7015, 7016, 7017, 6909, 7314, 7317, 6948, 7294, 7296, 7298, 7300, 6912, 7262, 7264, 7266, 7268, 7270, 6920, 7302, 7304, 7306, 7308, 6950, 6953, 6956, 6914, 7272, 7274, 6946, 7320, 7322, 7324, 7326, 7328, 6923, 6943, 7211, 7213, 7215, 7217, 7219, 7221, 7223, 7225, 7227, 6901, 6903, 6905, 6907, 774, 775, 776, 777, 778, 779, 3503, 6951, 6954, 6957 }; return new int[] { 761, 762, 763, 764, 765, 766, 3505, 3598, 6969, 7259, 7260, 6964, 7249, 7251, 6960, 7241, 7243, 6962, 7245, 7247, 6966, 7253, 7255, 6958, 7237, 7239, 6915, 7277, 7278, 7279, 7280, 7018, 7019, 7020, 6908, 7313, 7316, 6947, 7293, 7295, 7297, 7299, 6911, 7261, 7263, 7265, 7267, 7269, 6919, 7301, 7303, 7305, 7307, 6949, 6952, 6955, 6913, 7271, 7273, 6945, 7319, 7321, 7323, 7325, 7327, 6922, 6942, 7210, 7212, 7214, 7216, 7218, 7220, 7222, 7224, 7226, 6900, 6902, 6904, 6906, 768, 769, 770, 771, 772, 773, 3504, 6968, 7257, 7258, 6965, 7250, 7252, 6961, 7242, 7244, 6963, 7246, 7248, 6967, 7254, 7256, 6859, 7238, 7240, 6916, 7281, 7282, 7283, 7284, 7015, 7016, 7017, 6909, 7314, 7317, 6948, 7294, 7296, 7298, 7300, 6912, 7262, 7264, 7266, 7268, 7270, 6920, 7302, 7304, 7306, 7308, 6950, 6953, 6956, 6914, 7272, 7274, 6946, 7320, 7322, 7324, 7326, 7328, 6923, 6943, 7211, 7213, 7215, 7217, 7219, 7221, 7223, 7225, 7227, 6901, 6903, 6905, 6907, 774, 775, 776, 777, 778, 779, 3503, 6951, 6954, 6957 };
} }
} }
@@ -19,11 +19,6 @@ public final class PetDetails {
*/ */
private double growth = 0.0; private double growth = 0.0;
/**
* The individual, an in principle arbitrary integer read off of the item's charge slot.
*/
private int individual;
/** /**
* Constructs a new {@code PetDetails} {@code Object}. * Constructs a new {@code PetDetails} {@code Object}.
* @param growth The growth value. * @param growth The growth value.
@@ -64,6 +59,13 @@ public final class PetDetails {
return hunger; return hunger;
} }
/**
* Sets the hunger. (You probably want to use updateHunger() instead.)
*/
public void setHunger(double value) {
this.hunger = value;
}
/** /**
* Gets the growth. * Gets the growth.
* @return The growth. * @return The growth.
@@ -73,18 +75,9 @@ public final class PetDetails {
} }
/** /**
* Sets the individual. * Sets the growth. (You probably want to use updateGrowth() instead.)
* @param individual The individual to set.
*/ */
public void setIndividual(int individual) { public void setGrowth(double value) {
this.individual = individual; this.growth = value;
}
/**
* Gets the individual.
* @return The individual.
*/
public int getIndividual() {
return individual;
} }
} }
@@ -180,61 +180,6 @@ public enum Pets {
*/ */
BABY_DRAGON(12469, 12470, -1, 6900, 6901, -1, 0.0052, 99, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 321, 363, 341, 15264, 345, 377, 353, 389, 7944, 349, 331, 327, 395, 383, 317, 371, 335, 359, 15264, 15270), BABY_DRAGON_1(12471, 12472, -1, 6902, 6903, -1, 0.0052, 99, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 321, 363, 341, 15264, 345, 377, 353, 389, 7944, 349, 331, 327, 395, 383, 317, 371, 335, 359, 15264, 15270), BABY_DRAGON_2(12473, 12474, -1, 6904, 6905, -1, 0.0052, 99, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 321, 363, 341, 15264, 345, 377, 353, 389, 7944, 349, 331, 327, 395, 383, 317, 371, 335, 359, 15264, 15270), BABY_DRAGON_3(12475, 12476, -1, 6906, 6907, -1, 0.0052, 99, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 321, 363, 341, 15264, 345, 377, 353, 389, 7944, 349, 331, 327, 395, 383, 317, 371, 335, 359, 15264, 15270); BABY_DRAGON(12469, 12470, -1, 6900, 6901, -1, 0.0052, 99, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 321, 363, 341, 15264, 345, 377, 353, 389, 7944, 349, 331, 327, 395, 383, 317, 371, 335, 359, 15264, 15270), BABY_DRAGON_1(12471, 12472, -1, 6902, 6903, -1, 0.0052, 99, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 321, 363, 341, 15264, 345, 377, 353, 389, 7944, 349, 331, 327, 395, 383, 317, 371, 335, 359, 15264, 15270), BABY_DRAGON_2(12473, 12474, -1, 6904, 6905, -1, 0.0052, 99, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 321, 363, 341, 15264, 345, 377, 353, 389, 7944, 349, 331, 327, 395, 383, 317, 371, 335, 359, 15264, 15270), BABY_DRAGON_3(12475, 12476, -1, 6906, 6907, -1, 0.0052, 99, 2132, 2134, 2136, 2138, 10816, 9986, 9978, 321, 363, 341, 15264, 345, 377, 353, 389, 7944, 349, 331, 327, 395, 383, 317, 371, 335, 359, 15264, 15270);
// /**
// * Emperor's pets (that's right, MY pet"S").
// */
// GIANT_WOLPERTINGER(8888, -1, -1, 6990, -1, -1, 0.0, 99), DRAKAN(8889, -1, -1, 4794, -1, -1, 0.0, 99), DILL(8890, -1, -1, 7770, -1, -1, 0.0, 99),
// /**
// * Vexias pet.
// */
// IMP(9952, -1, -1, 1531, -1, -1, 0.0, 99), BIG_GUY(9951, -1, -1, 3101, -1, -1, 0.0, 99), LITTLE_GUY(8887, -1, -1, 5805, -1, -1, 0.0, 99),
//
// /**
// * Godwars boss pets.
// */
// KRIL_JR(14648, -1, -1, 8591, -1, -1, 0.0, 1), KREE_JR(14645, -1, -1, 8592, -1, -1, 0.0, 1), ZILYANA_JR(14647, -1, -1, 8593, -1, -1, 0.0, 1), GRAARDOOR_JR(14646, -1, -1, 8594, -1, -1, 0.0, 1),
//
// /**
// * Classic boss pets.
// */
// CHAOS_ELE_JR(14638, -1, -1, 8595, -1, -1, 0.0, 1), PRINCE_BLACK_DRAGON(14649, -1, -1, 8596, -1, -1, 0.0, 1), BABY_MOLE(14642, -1, -1, 8601, -1, -1, 0.0, 1), KQ_FORM_1(14643, -1, -1, 8602, -1, -1, 0.0, 1), KQ_FORM_2(14650, -1, -1, 8603, -1, -1, 0.0, 1), DARK_CORE(14653, -1, -1, 8630, -1, -1, 0.0, 1),
//
// /**
// * The boss pets for the Dagannoths
// */
// DAGANNOTH_SUPREME(14639, -1, -1, 8605, -1, -1, 0.0, 1), DAGANNOTH_PRIME(14640, -1, -1, 8606, -1, -1, 0.0, 1), DAGANNOTH_REX(14641, -1, -1, 8607, -1, -1, 0.0, 1),
// /**
// * The new OSRS bosses.
// */
// CALLISTO_CUB(14658, -1, -1, 8597, -1, -1, 0.0, 1),
// SCORPIA_JR(14661, -1, -1, 8598, -1, -1, 0.0, 1),
// VENENATIS_JR(14657, -1, -1, 8654, -1, -1, 0.0, 1),
// VETION_JR(14659, -1, -1, 8600, -1, -1, 0.0, 1),
// VETION_JR_2(14660, -1, -1, 8654, -1, -1, 0.0, 1),
// RELEASE_THE_KRAKEN(14651, -1, -1, 8608, -1, -1, 0.0, 1),
// SMOKE_DEVIL(14644, -1, -1, 8609, -1, -1, 0.0, 1),
// SNAKELING_YELLOW(14654, -1, -1, 8626, -1, -1, 0.0, 1),
// SNAKELING_ORANGE(14655, -1, -1, 8627, -1, -1, 0.0, 1),
// SNAKELING_PURPLE(14656, -1, -1, 8628, -1, -1, 0.0, 1),
// /**
// * The boss pet representing the likeness of the Penance Queen
// */
// DRAMA_QUEEN(14652, -1, -1, 8604, -1, -1, 0.0, 1),
// /**
// * The skilling pets
// */
// BEAVER(14821, -1, -1, 8635, -1, -1, 0.0, 1),
// ROCK_GOLEM(14822, -1, -1, 8637, -1, -1, 0.0, 1),
// BABY_RED_CHINCHOMPA(14823, -1, -1, 8643, -1, -1, 0.0, 1),
// BABY_GREY_CHINCHOMPA(14824, -1, -1, 8644, -1, -1, 0.0, 1),
// BABY_BLACK_CHINCHOMPA(14825, -1, -1, 8657, -1, -1, 0.0, 1),
// BABY_GOLD_CHINCHOMPA(14826, -1, -1, 8658, -1, -1, 0.0, 1),
// HERON(14827, -1, -1, 8647, -1, -1, 0.0, 1),
// TZREK_JAD(14828, -1, -1, 8650, -1, -1, 0.0, 1);
/** /**
* The baby pets mapping. * The baby pets mapping.
*/ */
+1 -1
View File
@@ -18,7 +18,7 @@ class ServerConstants {
var NOAUTH_DEFAULT_ADMIN: Boolean = true var NOAUTH_DEFAULT_ADMIN: Boolean = true
@JvmField @JvmField
var CURRENT_SAVEFILE_VERSION = 1 var CURRENT_SAVEFILE_VERSION = 2
@JvmField @JvmField
var DAILY_ACCOUNT_LIMIT = 3 var DAILY_ACCOUNT_LIMIT = 3
+36
View File
@@ -326,6 +326,7 @@ fun addItem(player: Player, id: Int, amount: Int = 1, container: Container = Con
* @param player the player whose container to modify * @param player the player whose container to modify
* @param slot the slot to use * @param slot the slot to use
* @param item the item to replace the slot with * @param item the item to replace the slot with
* @param currentItem the current item that is being replaced
* @param container the Container to modify * @param container the Container to modify
* @return the item that was previously in the slot, or null if none. * @return the item that was previously in the slot, or null if none.
*/ */
@@ -358,6 +359,41 @@ fun replaceSlot(player: Player, slot: Int, item: Item, currentItem: Item? = null
return null return null
} }
/**
* Replaces all items a player owns anywhere (equipment, inventory, bank, second bank)
* @param player the player whose inventory to remove the item from
* @param itemId the item ID to replace
* @param replaceId the replacement item ID
* @author Player Name
*/
fun replaceAllItems(player: Player, itemId: Int, replaceId: Int) {
val item = Item(itemId)
for (container in arrayOf(player.inventory, player.equipment, player.bankPrimary, player.bankSecondary)) {
val hasItems = container.getAll(item)
if (!item.definition.isStackable && (container == player.inventory || container == player.equipment)) {
for (target in hasItems) {
val newItem = Item(replaceId, target.amount)
container.replace(newItem, target.slot, true)
}
} else {
if (hasItems.size > 0) {
val target = hasItems[0]
var count = 0
for (x in hasItems) {
count += x.amount
}
val newItem = Item(replaceId, count)
container.replace(newItem, target.slot, true)
}
if (hasItems.size > 1) {
for (i in 1 until hasItems.size) {
container.remove(hasItems[i], hasItems[i].slot, true)
}
}
}
}
}
/** /**
* Add an item with a variable quantity or drop it if a player does not have enough space * Add an item with a variable quantity or drop it if a player does not have enough space
* @param player the player whose inventory to add to * @param player the player whose inventory to add to
@@ -1,94 +0,0 @@
package core.game.node.entity.npc;
import core.cache.def.impl.NPCDefinition;
import core.game.dialogue.DialoguePlugin;
import content.global.skill.summoning.familiar.Familiar;
import content.global.skill.summoning.pet.Pets;
import core.game.interaction.OptionHandler;
import core.game.node.Node;
import core.game.node.entity.player.Player;
import core.game.node.item.Item;
import core.plugin.Plugin;
import core.plugin.ClassScanner;
import core.tools.RandomFunction;
/**
* A superclass plugin for any pets that have a metamorphosis option.
* @author Empathy
*
*/
public abstract class Metamorphosis extends OptionHandler {
/**
* The ids of the possible npcs to metamorph into.
*/
protected int[] ids;
/**
* Constructs a new {@code Metamorphosis} {@code Object}.
* @param ids the id to transform.
*/
public Metamorphosis(int...ids) {
this.ids = ids;
}
/**
* The dialogue plugin for the pet.
* @return the plugin.
*/
public abstract DialoguePlugin getDialoguePlugin();
@Override
public Plugin<Object> newInstance(Object arg) throws Throwable {
for (int id : getIds()) {
NPCDefinition.forId(id).getHandlers().put("option:metamorphosis", this);
}
if (getDialoguePlugin() != null) {
ClassScanner.definePlugin(getDialoguePlugin());
}
return this;
}
@Override
public boolean handle(Player player, Node node, String option) {
Familiar familiar = (Familiar) node;
switch (option) {
case "metamorphosis":
if (player.getFamiliarManager().isOwner(familiar)) {
int newNpc = player.getFamiliarManager().getFamiliar().getId();
while (newNpc == player.getFamiliarManager().getFamiliar().getId()) {
newNpc = getRandomNpcId();
}
for (Pets p : Pets.values()) {
if (p.getBabyNpcId() == newNpc) {
player.getFamiliarManager().morphPet(new Item(p.getBabyItemId()), false, player.getFamiliarManager().getFamiliar().getLocation());
break;
}
}
player.getPacketDispatch().sendMessage("You transform your " + player.getFamiliarManager().getFamiliar().getName() + "!");
} else {
player.getPacketDispatch().sendMessage("This is not your familiar.");
}
break;
}
return true;
}
/**
* Gets a random npc id.
* @return
*/
public int getRandomNpcId() {
int i = RandomFunction.getRandom(getIds().length - 1);
return getIds()[i];
}
/**
* Gets the npc ids.
* @return the id.
*/
public int[] getIds() {
return ids;
}
}
@@ -53,6 +53,7 @@ class PlayerSaveParser(val player: Player) {
} }
fun parseData() { fun parseData() {
parseVersion()
parseCore() parseCore()
parseAttributes() parseAttributes()
parseSkills() parseSkills()
@@ -77,7 +78,6 @@ class PlayerSaveParser(val player: Player) {
parseStatistics() parseStatistics()
parseAchievements() parseAchievements()
parsePouches() parsePouches()
parseVersion()
} }
fun runContentHooks() fun runContentHooks()
@@ -36,6 +36,7 @@ class PlayerSaver (val player: Player){
} }
fun populate(): JSONObject { fun populate(): JSONObject {
val saveFile = JSONObject() val saveFile = JSONObject()
saveVersion(saveFile)
saveCoreData(saveFile) saveCoreData(saveFile)
saveSkills(saveFile) saveSkills(saveFile)
saveSettings(saveFile) saveSettings(saveFile)
@@ -56,7 +57,6 @@ class PlayerSaver (val player: Player){
saveStatManager(saveFile) saveStatManager(saveFile)
saveAttributes(saveFile) saveAttributes(saveFile)
savePouches(saveFile) savePouches(saveFile)
saveVersion(saveFile)
contentHooks.forEach { it.savePlayer(player, saveFile) } contentHooks.forEach { it.savePlayer(player, saveFile) }
return saveFile return saveFile
} }
@@ -278,17 +278,21 @@ class PlayerSaver (val player: Player){
fun saveFamiliarManager(root: JSONObject){ fun saveFamiliarManager(root: JSONObject){
val familiarManager = JSONObject() val familiarManager = JSONObject()
val petDetails = JSONArray() val petDetails = JSONObject()
player.familiarManager.petDetails.map { player.familiarManager.petDetails.map {
val detail = JSONObject() val petId = it.key
detail.put("petId",it.key.toString()) val petData = JSONArray()
detail.put("hunger",it.value.hunger.toString()) for (v in it.value) {
detail.put("growth",it.value.growth.toString()) val pet = JSONObject()
petDetails.add(detail) pet.put("hunger",v.hunger.toString())
pet.put("growth",v.growth.toString())
petData.add(pet)
}
petDetails.put(petId.toString(), petData)
} }
familiarManager.put("petDetails",petDetails) familiarManager.put("petDetails",petDetails)
if(player.familiarManager.hasPet()){ if(player.familiarManager.hasPet()){
familiarManager.put("currentPet",(player.familiarManager.familiar as Pet).getItemIdHash().toString()) familiarManager.put("currentPet",(player.familiarManager.familiar as Pet).getItemId().toString())
} else if (player.familiarManager.hasFamiliar()){ } else if (player.familiarManager.hasFamiliar()){
val familiar = JSONObject() val familiar = JSONObject()
familiar.put("originalId",player.familiarManager.familiar.originalId.toString()) familiar.put("originalId",player.familiarManager.familiar.originalId.toString())
@@ -1,25 +1,22 @@
package core.game.node.entity.player.info.login package core.game.node.entity.player.info.login
import content.global.skill.summoning.pet.Pets
import core.ServerConstants import core.ServerConstants
import core.api.* import core.api.*
import core.game.node.entity.player.Player import core.game.node.entity.player.Player
import core.game.node.item.Item import core.game.node.item.Item
import org.rs09.consts.Items import org.rs09.consts.Items
/** /**
* Runs one-time save-version-related hooks. * Runs one-time save-version-related hooks.
* @author Player Name * @author Player Name
*/ */
class SaveVersionHooks : LoginListener { class SaveVersionHooks : LoginListener {
override fun login(player: Player) { override fun login(player: Player) {
if (player.version < ServerConstants.CURRENT_SAVEFILE_VERSION) { if (player.version < ServerConstants.CURRENT_SAVEFILE_VERSION) {
sendMessage(player, "<col=CC6600>Migrating save file version ${player.version} to current save file version ${ServerConstants.CURRENT_SAVEFILE_VERSION}.</col>")
// Perform actual migrations if (player.version < 1) { // GL !1811
if (player.version < 1) { // GL #1811
// Give out crafting hoods if the player bought any crafting capes when the hoods were not obtainable // Give out crafting hoods if the player bought any crafting capes when the hoods were not obtainable
var hasHoods = 0 var hasHoods = 0
var hasCapes = 0 var hasCapes = 0
@@ -60,10 +57,18 @@ class SaveVersionHooks : LoginListener {
} }
} }
// Finish up if (player.version < 2) { //GL !1799
player.version = ServerConstants.CURRENT_SAVEFILE_VERSION // Most of the migration for this MR happens in FamiliarManager.java, but we fix up any pet items here
sendMessage(player, "<col=CC6600>Save file migration complete. Happy scaping!</col>") val pets = Pets.values()
for (pet in pets) {
for (id in arrayOf(pet.babyItemId, pet.grownItemId, pet.overgrownItemId)) {
replaceAllItems(player, id, id)
// The trick here is that replaceAllItems ignores the item charge value, and will hence cause it to be lost, making all pets authentically stack again
}
} }
} }
player.version = ServerConstants.CURRENT_SAVEFILE_VERSION
}
}
} }
@@ -196,14 +196,6 @@ public class Item extends Node{
return idHash; return idHash;
} }
/**
* Sets the id hash.
* @param hash the hash to set
*/
public void setIdHash(int hash) {
this.idHash = hash;
}
/** /**
* Checks if the item has a wrapper plugin. * Checks if the item has a wrapper plugin.
* @return {@code True} if so. * @return {@code True} if so.