From 4799069ebd9455afbbef9288152ef0a4ae068e43 Mon Sep 17 00:00:00 2001 From: randy Date: Sat, 22 Mar 2025 21:23:28 -0600 Subject: [PATCH] Summoning rework Pouches are not consumed. Points are not lost when summoning. However, lifespan is directly tied to summoning points, and the familiar despawns at zero points. Created a new pouch storage that can be accessed from the summoning tab by pressing "call pet" when no familiar is active. This allows freely switching familiars as long as points are maintained. Special move points start at empty instead of full, and replenish 1 point every 3 ticks instead of 15 points every 50 ticks. --- .../skill/summoning/SummoningTabListener.kt | 5 +- .../familiar/BurdenInterfacePlugin.java | 4 + .../skill/summoning/familiar/Familiar.java | 23 +++-- .../summoning/familiar/FamiliarManager.java | 28 ++++-- .../familiar/SnowscapeFamiliarInterface.kt | 96 +++++++++++++++++++ Server/src/main/core/api/utils/Permadeath.kt | 1 + .../core/game/node/entity/player/Player.java | 2 + .../player/info/login/PlayerSaveParser.kt | 2 + .../entity/player/info/login/PlayerSaver.kt | 2 + .../entity/player/link/InterfaceManager.java | 8 +- 10 files changed, 147 insertions(+), 24 deletions(-) create mode 100644 Server/src/main/content/global/skill/summoning/familiar/SnowscapeFamiliarInterface.kt diff --git a/Server/src/main/content/global/skill/summoning/SummoningTabListener.kt b/Server/src/main/content/global/skill/summoning/SummoningTabListener.kt index c47862b62..ee39b8470 100644 --- a/Server/src/main/content/global/skill/summoning/SummoningTabListener.kt +++ b/Server/src/main/content/global/skill/summoning/SummoningTabListener.kt @@ -4,6 +4,8 @@ import content.global.skill.summoning.pet.Pet import core.api.sendMessage import core.game.interaction.InterfaceListener +import content.global.skill.summoning.familiar.SnowscapeFamiliarInterface + class SummoningTabListener : InterfaceListener { override fun defineInterfaceListeners() { on(662) { player, _, opcode, buttonID, _, _ -> @@ -12,7 +14,8 @@ class SummoningTabListener : InterfaceListener { if (player.familiarManager.hasFamiliar()) { player.familiarManager.familiar.call() } else { - player.getPacketDispatch().sendMessage("You don't have a follower.") + SnowscapeFamiliarInterface.Companion.openInterface(player) + //player.getPacketDispatch().sendMessage("You don't have a follower.") } } 67 -> { diff --git a/Server/src/main/content/global/skill/summoning/familiar/BurdenInterfacePlugin.java b/Server/src/main/content/global/skill/summoning/familiar/BurdenInterfacePlugin.java index 275b4abf5..84de00f93 100644 --- a/Server/src/main/content/global/skill/summoning/familiar/BurdenInterfacePlugin.java +++ b/Server/src/main/content/global/skill/summoning/familiar/BurdenInterfacePlugin.java @@ -32,6 +32,10 @@ public final class BurdenInterfacePlugin extends ComponentPlugin { if (getAttribute(player, "openSatchel", null) != null) { SnowscapeSatchelListener.Companion.satchelInterfaceAction(player, component, opcode, button, slot, itemId); return true; + } + if (getAttribute(player, "openSummonStorage", false)) { + SnowscapeFamiliarInterface.Companion.interfaceAction(player, component, opcode, button, slot, itemId); + return true; } if (!player.getFamiliarManager().hasFamiliar() || !player.getFamiliarManager().getFamiliar().isBurdenBeast()) { return false; diff --git a/Server/src/main/content/global/skill/summoning/familiar/Familiar.java b/Server/src/main/content/global/skill/summoning/familiar/Familiar.java index 064de882c..636787050 100644 --- a/Server/src/main/content/global/skill/summoning/familiar/Familiar.java +++ b/Server/src/main/content/global/skill/summoning/familiar/Familiar.java @@ -36,6 +36,8 @@ import java.util.List; import static core.api.ContentAPIKt.*; +import core.game.component.Component; + /** * Represents a familiar. * @author Emperor @@ -80,7 +82,7 @@ public abstract class Familiar extends NPC implements Plugin { /** * The amount of special points left. */ - protected int specialPoints = 60; + protected int specialPoints = 0; /** * The pouch id. @@ -178,7 +180,7 @@ public abstract class Familiar extends NPC implements Plugin { this.pointsPerTick = 0.0; } else { //int drain = pouch.getLevelRequired() - pouch.getSummonCost() + 1; - // Snowscape: removing the initial drain and making it drain over the life of the familiar. Removing the +1 makes the final drain happen on the last tick of the familiar's life. That works for our new system, as the familiar timer won't start until that final tick happens. + // Snowscape: removing the initial drain and making it drain over the life of the familiar. Removing the +1 makes the final drain happen on the last tick of the familiar's life. That works for our new system. int drain = pouch.getLevelRequired(); this.pointsPerTick = (double) drain / maximumTicks; } @@ -225,21 +227,20 @@ public abstract class Familiar extends NPC implements Plugin { @Override public void handleTickActions() { - //Snowscape: only count down the familiar timer if summoning points are zero, and give some summoning exp every minute a familiar is summoned. - if (owner.getSkills().getLevel(Skills.SUMMONING) == 0) { - ticks--; - } + //Snowscape: ticks are calculated from the summoning points (including fractional drain), so the familiar will despawn when points reach zero, but the timer goes up if points are restored + //ticks--; + ticks = Double.valueOf(maximumTicks*(owner.getSkills().getLevel(Skills.SUMMONING)-fracDrain)/pouch.getLevelRequired()).intValue(); if (getWorldTicks() % 100 == 0) { owner.getSkills().addExperience(Skills.SUMMONING, pouch.getLevelRequired()+10, true); } fracDrain += pointsPerTick; - if (fracDrain > 1.0 && ticks > 0) { + if (fracDrain > 1.0) { fracDrain -= 1.0; owner.getSkills().updateLevel(Skills.SUMMONING, -1, 0); } - if (ticks % 50 == 0) { - updateSpecialPoints(-15); + if (getWorldTicks() % 3 == 0) { + updateSpecialPoints(-1); if (!getText().isEmpty()) { super.sendChat(getText()); } @@ -258,6 +259,7 @@ public abstract class Familiar extends NPC implements Plugin { } else { owner.getPacketDispatch().sendMessage("Your familiar has vanished."); } + playAudio(owner, Sounds.PRAYER_DRAIN_2672); dismiss(); return; } @@ -665,7 +667,8 @@ public abstract class Familiar extends NPC implements Plugin { setVarp(owner, 1175, 182986); setVarp(owner, 1174, -1); owner.getAppearance().sync(); - owner.getInterfaceManager().setViewedTab(3); + owner.getInterfaceManager().openTab(new Component(662)); + owner.getInterfaceManager().setViewedTab(7); } /** 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 94802a146..c9a9cb9fa 100644 --- a/Server/src/main/content/global/skill/summoning/familiar/FamiliarManager.java +++ b/Server/src/main/content/global/skill/summoning/familiar/FamiliarManager.java @@ -142,7 +142,13 @@ public final class FamiliarManager { ((BurdenBeast) familiar).container.parse(famInv); } familiar.setAttribute("hp",Integer.parseInt( currentFamiliar.get("lifepoints").toString())); - } + } else { + //Snowscape: Copied from Familiar.dismiss(), this clears the fields on the summoning tab if no familiar is loaded + setVarp(player, 448, -1); + setVarp(player, 1176, 0); + setVarp(player, 1175, 182986); + setVarp(player, 1174, -1); + } } /** @@ -164,12 +170,12 @@ public final class FamiliarManager { public void summon(Item item, boolean pet, boolean deleteItem) { boolean renew = false; if (hasFamiliar()) { - if (familiar.getPouchId() == item.getId()) { - renew = true; - } else { + //if (familiar.getPouchId() == item.getId()) { + // renew = true; + //} else { player.getPacketDispatch().sendMessage("You already have a follower."); return; - } + //} } if (player.getZoneMonitor().isRestricted(ZoneRestriction.FOLLOWERS) && !player.getLocks().isLocked("enable_summoning")) { player.getPacketDispatch().sendMessage("This is a Summoning-free area."); @@ -193,6 +199,10 @@ public final class FamiliarManager { return; } */ + if (player.getSkills().getLevel(Skills.SUMMONING) < 1) { + player.getPacketDispatch().sendMessage("You need at least one Summoning point to summon this familiar."); + return; + } final int npcId = pouch.getNpcId(); Familiar fam = !renew ? FAMILIARS.get(npcId) : familiar; if (fam == null) { @@ -207,11 +217,11 @@ public final class FamiliarManager { return; } } - if (!player.getInventory().remove(item)) { - return; - } + //if (!player.getInventory().remove(item)) { + // return; + //} //player.getSkills().updateLevel(Skills.SUMMONING, -pouch.getSummonCost(), 0); - player.getSkills().addExperience(Skills.SUMMONING, pouch.getSummonExperience()); + //player.getSkills().addExperience(Skills.SUMMONING, pouch.getSummonExperience()); if (!renew) { familiar = fam; spawnFamiliar(); diff --git a/Server/src/main/content/global/skill/summoning/familiar/SnowscapeFamiliarInterface.kt b/Server/src/main/content/global/skill/summoning/familiar/SnowscapeFamiliarInterface.kt new file mode 100644 index 000000000..388b746a0 --- /dev/null +++ b/Server/src/main/content/global/skill/summoning/familiar/SnowscapeFamiliarInterface.kt @@ -0,0 +1,96 @@ +package content.global.skill.summoning.familiar + +import core.api.* +import core.game.container.Container +import content.global.skill.summoning.familiar.BurdenContainerListener +//import core.game.node.Node +import core.game.node.entity.player.Player +import core.game.node.item.Item +//import core.game.interaction.InteractionListener +//import core.game.interaction.IntType +//import org.json.simple.JSONArray +//import org.json.simple.JSONObject +//import org.json.simple.parser.JSONParser +import org.rs09.consts.Items + +import core.game.component.Component +import core.game.component.CloseEvent +import core.game.container.access.InterfaceContainer + +/* Custom Snowscape class. Allows storing summoning pouches in a special storage that can be accessed by using the "call familiar" button when no familiars are summoned. +* Familiars can be summoned from this storage. +* Designed around the snowscape summoning rework, where pouches are not consumed when summoning +*/ + +class SnowscapeFamiliarInterface { + companion object { + + + //Function to handle constructing the interface + public fun openInterface(player: Player) { + val storage = player.summoningPouches + if (storage.getListeners().count() < 1) storage.register(BurdenContainerListener(player)) + + player.getInterfaceManager().open(Component(671)).setCloseEvent(CloseEvent { player, component -> + player.getInterfaceManager().closeSingleTab() + removeAttribute(player, "openSummonStorage") + //Summoning tab cannot be interacted with until it's closed and re-opened for some reason + player.getInterfaceManager().removeTabs(7) + player.getInterfaceManager().openTab(Component(662)) + player.getInterfaceManager().setViewedTab(7) + return@CloseEvent true + } + ) + setAttribute(player, "openSummonStorage", true) + storage.shift() + player.getInterfaceManager().openSingleTab(Component(665)) + InterfaceContainer.generateItems(player, player.getInventory().toArray(), arrayOf("Add"),665,0,7,4,93) + InterfaceContainer.generateItems(player, storage.toArray(), arrayOf("Remove","Summon"),671,27,5,6,30) + } + + + fun interfaceAction(player: Player, component: Component, opcode: Int, button: Int, slot: Int, itemId: Int) { + val inventory = component.getId() == 665 + val container = if (inventory) player.getInventory() else player.summoningPouches + val item = if (slot >= 0 && slot < container.capacity()) container.get(slot) else null + if (item == null && button != 29) return + + if (opcode == 9) player.sendMessage(item!!.getDefinition().getExamine()) + + if (inventory) { + when (opcode) { + 155 -> addPouch(player, item!!) + } + } else { + when (opcode) { + 155 -> if (button != 29) summonPouch(player, item!!) + 196 -> removePouch(player, item!!) + } + } + } + + private fun addPouch(player: Player, item: Item) { + if (!hasOption(item, "Summon")){ + sendMessage(player, "You can only add summoning pouches.") + return + } else if (player.summoningPouches.containsItem(item)) { + sendMessage(player, "You have already added this familiar's pouch.") + return + } else if (player.summoningPouches.add(item)) { + player.getInventory().remove(item) + } + } + + private fun removePouch(player: Player, item: Item) { + if (player.getInventory().add(item)) { + player.summoningPouches.remove(item) + } + } + + private fun summonPouch(player: Player, item: Item) { + player.getInterfaceManager().close() + SummonFamiliarPlugin().handle(player, item, "summon") + } + + } +} \ No newline at end of file diff --git a/Server/src/main/core/api/utils/Permadeath.kt b/Server/src/main/core/api/utils/Permadeath.kt index 1e7f13e9f..cbd442bc7 100644 --- a/Server/src/main/core/api/utils/Permadeath.kt +++ b/Server/src/main/core/api/utils/Permadeath.kt @@ -49,6 +49,7 @@ fun permadeath(target: Player) { target.goldSatchel.clear() target.runeSatchel.clear() target.blackSatchel.clear() + target.summoningPouches.clear() // Skills target.skills = Skills(target) diff --git a/Server/src/main/core/game/node/entity/player/Player.java b/Server/src/main/core/game/node/entity/player/Player.java index 06436975c..6416acb37 100644 --- a/Server/src/main/core/game/node/entity/player/Player.java +++ b/Server/src/main/core/game/node/entity/player/Player.java @@ -319,6 +319,8 @@ public class Player extends Entity { public final Container blackSatchel = new Container(30, ContainerType.ALWAYS_STACK); public final Container goldSatchel = new Container(30, ContainerType.ALWAYS_STACK); public final Container runeSatchel = new Container(30, ContainerType.ALWAYS_STACK); + // The summoning pouch storage + public final Container summoningPouches = new Container(30); /** * Constructs a new {@code Player} {@code Object}. diff --git a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaveParser.kt b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaveParser.kt index cb0e151f4..2e661c782 100644 --- a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaveParser.kt +++ b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaveParser.kt @@ -387,6 +387,8 @@ class PlayerSaveParser(val player: Player) { val runeSatchel = snowscapeData["runeSatchel"] if (runeSatchel != null) player.runeSatchel.parse(runeSatchel as JSONArray) + val summoningPouches = snowscapeData["summoningPouches"] + if (summoningPouches != null) player.summoningPouches.parse(summoningPouches as JSONArray) } } diff --git a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt index 1f1e3d566..15ab459af 100644 --- a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt +++ b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt @@ -656,6 +656,8 @@ class PlayerSaver (val player: Player){ snowscapeData.put("goldSatchel", saveContainer(player.goldSatchel)) snowscapeData.put("runeSatchel", saveContainer(player.runeSatchel)) + snowscapeData.put("summoningPouches", saveContainer(player.summoningPouches)) + root.put("snowscape_data",snowscapeData) } } diff --git a/Server/src/main/core/game/node/entity/player/link/InterfaceManager.java b/Server/src/main/core/game/node/entity/player/link/InterfaceManager.java index 4a0fb00ae..eaa9ca5da 100644 --- a/Server/src/main/core/game/node/entity/player/link/InterfaceManager.java +++ b/Server/src/main/core/game/node/entity/player/link/InterfaceManager.java @@ -358,9 +358,9 @@ public final class InterfaceManager { openTab(6, new Component(player.getSpellBookManager().getSpellBook())); // Magic break; case 7: - if (player.getFamiliarManager().hasFamiliar()) { + //if (player.getFamiliarManager().hasFamiliar()) { openTab(7, new Component(662)); - } + //} break; default: openTab(i, new Component(DEFAULT_TABS[i])); @@ -393,9 +393,9 @@ public final class InterfaceManager { openTab(4, new Component(Components.WORNITEMS_387)); // Equipment openTab(5, new Component(Components.PRAYER_271)); // Prayer openTab(6, new Component(player.getSpellBookManager().getSpellBook())); // Magic - if (player.getFamiliarManager().hasFamiliar()) { + //if (player.getFamiliarManager().hasFamiliar()) { openTab(7, new Component(Components.LORE_STATS_SIDE_662)); // summoning. - } + //} openTab(8, new Component(Components.FRIENDS2_550)); // Friends openTab(9, new Component(Components.IGNORE2_551)); // Ignores openTab(10, new Component(Components.CLANJOIN_589)); // Clan chat