diff --git a/Server/data/configs/drop_tables.json b/Server/data/configs/drop_tables.json index b1929f67c..d79fda2df 100644 --- a/Server/data/configs/drop_tables.json +++ b/Server/data/configs/drop_tables.json @@ -2438,7 +2438,7 @@ "minAmount": "75", "weight": "1.0", "id": "556", - "maxAmount": "5" + "maxAmount": "75" }, { "minAmount": "10", @@ -19681,10 +19681,10 @@ "maxAmount": "1" }, { - "minAmount": "110", + "minAmount": "30", "weight": "5.0", "id": "313", - "maxAmount": "30" + "maxAmount": "110" }, { "minAmount": "1", @@ -19846,10 +19846,10 @@ "maxAmount": "1" }, { - "minAmount": "110", + "minAmount": "30", "weight": "5.0", "id": "313", - "maxAmount": "30" + "maxAmount": "110" }, { "minAmount": "1", @@ -48483,10 +48483,10 @@ "maxAmount": "2" }, { - "minAmount": "3", + "minAmount": "1", "weight": "500.0", "id": "7054", - "maxAmount": "1" + "maxAmount": "3" }, { "minAmount": "1", diff --git a/Server/data/configs/ground_spawns.json b/Server/data/configs/ground_spawns.json index e8765d2d9..989bbc066 100644 --- a/Server/data/configs/ground_spawns.json +++ b/Server/data/configs/ground_spawns.json @@ -491,10 +491,6 @@ "item_id": "2347", "loc_data": "{1,2934,3286,0,7209050}-{1,2579,3463,0,5242920}-{1,2975,3368,1,5898270}-{1,1953,4974,0,5898270}" }, - { - "item_id": "2351", - "loc_data": "{1,3062,3657,0,11141270}" - }, { "item_id": "2357", "loc_data": "{1,3192,9822,0,11141270}" diff --git a/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyInterface.kt b/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyInterface.kt index 9b3c607df..306dc6a84 100644 --- a/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyInterface.kt +++ b/Server/src/main/content/global/ame/events/sandwichlady/SandwichLadyInterface.kt @@ -3,6 +3,7 @@ package content.global.ame.events.sandwichlady import core.game.node.item.Item import org.rs09.consts.Items import core.game.interaction.InterfaceListener +import content.global.ame.RandomEventManager class SandwichLadyInterface : InterfaceListener { @@ -17,6 +18,11 @@ class SandwichLadyInterface : InterfaceListener { override fun defineInterfaceListeners() { on(SANDWICH_INTERFACE){player, _, _, buttonID, _, _ -> + val event = RandomEventManager.getInstance(player)!!.event + if (event == null) { + player.interfaceManager.close() + return@on true + } val item = when(buttonID) { 7 -> {Item(baguette)} @@ -31,8 +37,8 @@ class SandwichLadyInterface : InterfaceListener { player.setAttribute("sandwich-lady:choice",item.id) player.interfaceManager.close() - player.dialogueInterpreter.open(SandwichLadyDialogue(true), content.global.ame.RandomEventManager.getInstance(player)!!.event) + player.dialogueInterpreter.open(SandwichLadyDialogue(true), event) return@on true } } -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/ame/events/shade/ShadeRENPC.kt b/Server/src/main/content/global/ame/events/shade/ShadeRENPC.kt index 8fecda80c..4d991afdf 100644 --- a/Server/src/main/content/global/ame/events/shade/ShadeRENPC.kt +++ b/Server/src/main/content/global/ame/events/shade/ShadeRENPC.kt @@ -4,16 +4,15 @@ import core.game.node.entity.Entity import core.game.node.entity.npc.NPC import content.global.ame.RandomEventNPC import core.api.utils.WeightBasedTable - - -val ids = 425..430 +import kotlin.math.* class ShadeRENPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(425){ + val ids = (425..430).toList() override fun talkTo(npc: NPC) {} override fun init() { super.init() - val index = (player.properties.combatLevel / 20) - 1 - val id = ids.toList()[index] + val index = max(0, min(ids.size, (player.properties.combatLevel / 20) - 1)) + val id = ids[index] this.transform(id) this.attack(player) sendChat("Leave this place!") @@ -29,4 +28,4 @@ class ShadeRENPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(4 super.tick() if(!player.viewport.currentPlane.npcs.contains(this)) this.clear() } -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/ame/events/zombie/ZombieRENPC.kt b/Server/src/main/content/global/ame/events/zombie/ZombieRENPC.kt index 1fed431b9..230d85ee8 100644 --- a/Server/src/main/content/global/ame/events/zombie/ZombieRENPC.kt +++ b/Server/src/main/content/global/ame/events/zombie/ZombieRENPC.kt @@ -4,16 +4,15 @@ import core.game.node.entity.Entity import core.game.node.entity.npc.NPC import content.global.ame.RandomEventNPC import core.api.utils.WeightBasedTable - - -val ids = 419..424 +import kotlin.math.* class ZombieRENPC(override var loot: WeightBasedTable? = null) : RandomEventNPC(419){ + val ids = (419..424).toList() override fun talkTo(npc: NPC) {} override fun init() { super.init() - val index = (player.properties.combatLevel / 20) - 1 - val id = ids.toList()[index] + val index = max(0, min(ids.size, (player.properties.combatLevel / 20) - 1)) + val id = ids[index] this.transform(id) this.attack(player) sendChat("Brainsssss!") @@ -29,4 +28,4 @@ class ZombieRENPC(override var loot: WeightBasedTable? = null) : RandomEventNPC( super.tick() if(!player.viewport.currentPlane.npcs.contains(this)) this.clear() } -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/handlers/iface/BookInterface.kt b/Server/src/main/content/global/handlers/iface/BookInterface.kt index 270a90c9a..ccb9d228d 100644 --- a/Server/src/main/content/global/handlers/iface/BookInterface.kt +++ b/Server/src/main/content/global/handlers/iface/BookInterface.kt @@ -190,4 +190,4 @@ class Page(vararg lines: BookLine) { class BookLine( val message: String, val child: Int -) \ No newline at end of file +) diff --git a/Server/src/main/content/global/handlers/iface/GenericItemSelect.kt b/Server/src/main/content/global/handlers/iface/GenericItemSelect.kt index 48b4b3318..beb2a297e 100644 --- a/Server/src/main/content/global/handlers/iface/GenericItemSelect.kt +++ b/Server/src/main/content/global/handlers/iface/GenericItemSelect.kt @@ -30,6 +30,7 @@ class GenericItemSelect : InterfaceListener { onClose(GENERIC_ITEM_SELECT_IFACE) {player, _ -> removeAttribute(player, "itemselect-callback") + removeAttribute(player, "itemselect-keepalive") return@onClose true } } @@ -56,7 +57,11 @@ class GenericItemSelect : InterfaceListener { } callback.invoke(slot, optionIndex) - removeAttribute (player, "itemselect-callback") - player.interfaceManager.closeSingleTab() + + if (!getAttribute(player, "itemselect-keepalive", false)) { + removeAttribute (player, "itemselect-callback") + removeAttribute (player, "itemselect-keepalive") + player.interfaceManager.closeSingleTab() + } } } diff --git a/Server/src/main/content/global/handlers/iface/ge/GrandExchangeInterface.java b/Server/src/main/content/global/handlers/iface/ge/GrandExchangeInterface.java index a6fa904c9..dd01103aa 100644 --- a/Server/src/main/content/global/handlers/iface/ge/GrandExchangeInterface.java +++ b/Server/src/main/content/global/handlers/iface/ge/GrandExchangeInterface.java @@ -18,7 +18,6 @@ import core.plugin.Initializable; import core.plugin.Plugin; import core.game.ge.GrandExchangeOffer; import core.game.ge.GrandExchangeRecords; -import content.region.kandarin.feldip.gutanoth.handlers.BogrogPouchSwapper; import core.game.world.GameWorld; /** @@ -132,12 +131,20 @@ public class GrandExchangeInterface extends ComponentPlugin { if (slot < 0 || slot >= (inventory ? 28 : GEItemSet.values().length)) { return; } - GEItemSet set = GEItemSet.values()[slot]; - Item item = inventory ? player.getInventory().get(slot) : new Item(set.getItemId()); - if (item == null) { - return; - } - if (opcode != 127 && inventory && ((set = GEItemSet.forId(item.getId())) == null) && !BogrogPouchSwapper.handle(player,opcode,slot,itemId)) { + + Item item; + GEItemSet set; + if (inventory) { + item = player.getInventory().get(slot); + if (item == null) return; + set = GEItemSet.forId(item.getId()); + if (set == null) return; + } else { + set = GEItemSet.values()[slot]; + item = new Item(set.getItemId()); + } + + if (opcode != 127 && inventory && set == null) { player.getPacketDispatch().sendMessage("This isn't a set item."); return; } diff --git a/Server/src/main/content/global/handlers/iface/ge/StockMarket.kt b/Server/src/main/content/global/handlers/iface/ge/StockMarket.kt index 7879f75ba..52f1de8dc 100644 --- a/Server/src/main/content/global/handlers/iface/ge/StockMarket.kt +++ b/Server/src/main/content/global/handlers/iface/ge/StockMarket.kt @@ -159,15 +159,11 @@ class StockMarket : InterfaceListener { 183 -> updateOfferValue(player, tempOffer, (GrandExchange.getRecommendedPrice(tempOffer.itemID) * 1.05).toInt()) 171 -> updateOfferValue(player, tempOffer, tempOffer.offeredValue - 1) 173 -> updateOfferValue(player, tempOffer, tempOffer.offeredValue + 1) - 185 -> sendInputDialogue(player, false, "Enter the amount:"){value -> + 185 -> sendInputDialogue(player, InputType.AMOUNT, "Enter the amount:"){value -> if (player.interfaceManager.chatbox.id == 389) { player.interfaceManager.openChatbox(Components.OBJDIALOG_389) } var s = value.toString() - s = s.replace("k", "000") - s = s.replace("K", "000") - s = s.replace("m", "000000") - s = s.replace("M", "000000") updateOfferValue(player, tempOffer, s.toInt()) setAttribute(player, "ge-temp", tempOffer) } diff --git a/Server/src/main/content/global/handlers/item/toys/DiangoReclaimInterface.java b/Server/src/main/content/global/handlers/item/toys/DiangoReclaimInterface.java index 71d5294de..82afc70ab 100644 --- a/Server/src/main/content/global/handlers/item/toys/DiangoReclaimInterface.java +++ b/Server/src/main/content/global/handlers/item/toys/DiangoReclaimInterface.java @@ -43,14 +43,10 @@ public class DiangoReclaimInterface extends ComponentPlugin { if(curOpen != null){ curOpen.close(player); } + Item[] reclaimables = getEligibleItems(player); + player.setAttribute("diango-reclaimables", reclaimables); //filter out items the player already has in their bank, inventory, or equipped - Item[] reclaimables = ITEMS.stream().filter(Objects::nonNull) - .filter(item -> !player.getEquipment().containsItem(item) && !player.getInventory().containsItem(item) && !player.getBank().containsItem(item) - && (item.getId() != 14654 - || (!(inInventory(player, 14655, 1) || inEquipment(player, 14656, 1)) && player.getAttribute("sotr:purchased",false)) - )) - .toArray(Item[]::new); //only send items if there are some to send if(reclaimables.length > 0) { @@ -68,18 +64,36 @@ public class DiangoReclaimInterface extends ComponentPlugin { @Override public boolean handle(Player player, Component component, int opcode, int button, int slot, int itemId) { - Item[] reclaimables = ITEMS.stream().filter(Objects::nonNull).filter(item -> !player.getEquipment().containsItem(item) && !player.getInventory().containsItem(item) && !player.getBank().containsItem(item)).toArray(Item[]::new); + Item[] reclaimables = player.getAttribute("diango-reclaimables", null); + if (reclaimables == null) + reclaimables = getEligibleItems(player); + + Item reclaimItem = reclaimables[slot]; + if (reclaimItem == null) { + player.sendMessage("Something went wrong there. Please try again."); + return true; + } + switch(opcode){ case 155: //interface item option 1 == take //add the clicked item to the player's inventory and refresh the interface - player.getInventory().add(reclaimables[slot]); + player.getInventory().add(reclaimItem); refresh(player); break; case 196: //interface item option 2 == examine //send the examine text for the item to the player - player.getPacketDispatch().sendMessage(reclaimables[slot].getDefinition().getExamine()); + player.getPacketDispatch().sendMessage(reclaimItem.getDefinition().getExamine()); break; } return false; } + + public static Item[] getEligibleItems (Player player) { + return ITEMS.stream().filter(Objects::nonNull) + .filter(item -> !player.getEquipment().containsItem(item) && !player.getInventory().containsItem(item) && !player.getBank().containsItem(item) + && (item.getId() != 14654 + || (!(inInventory(player, 14655, 1) || inEquipment(player, 14656, 1)) && player.getAttribute("sotr:purchased",false)) + )) + .toArray(Item[]::new); + } } diff --git a/Server/src/main/content/global/handlers/item/withobject/IncubatorPlugin.java b/Server/src/main/content/global/handlers/item/withobject/IncubatorPlugin.java index b0612293c..52c3b20da 100644 --- a/Server/src/main/content/global/handlers/item/withobject/IncubatorPlugin.java +++ b/Server/src/main/content/global/handlers/item/withobject/IncubatorPlugin.java @@ -34,7 +34,9 @@ public class IncubatorPlugin extends OptionHandler { @Override public boolean handle(Player player, Node node, String option) { - int inc = player.getAttribute("inc", -1); + int inc = player.getAttribute(IncubatorState.ATTR_INCUBATOR_PRODUCT, -1); + if (inc == -1) + inc = player.getAttribute("inc", -1); //deprecated, only here to avoid data loss switch (option) { case "take-egg": if (inc == -1) { @@ -54,6 +56,8 @@ public class IncubatorPlugin extends OptionHandler { GroundItemManager.create(egg.getProduct(),player); } player.removeAttribute("inc"); + player.removeAttribute(IncubatorState.ATTR_INCUBATOR_PRODUCT); + player.clearState("incubator"); } return true; case "inspect": diff --git a/Server/src/main/content/global/skill/construction/BuildOptionPlugin.java b/Server/src/main/content/global/skill/construction/BuildOptionPlugin.java index 3fb866ea6..69b0b1483 100644 --- a/Server/src/main/content/global/skill/construction/BuildOptionPlugin.java +++ b/Server/src/main/content/global/skill/construction/BuildOptionPlugin.java @@ -66,8 +66,8 @@ public final class BuildOptionPlugin extends OptionHandler { } Hotspot hotspot = player.getHouseManager().getHotspot(object); if (hotspot == null || !isBuildable(player, object, hotspot)) { - - log(this.getClass(), Log.ERR, "Construction (building): " + hotspot + " : " + object + " chunkX = " + object.getLocation().getChunkX() + ", chunkY = " + object.getLocation().getChunkY()); + //log demoted from ERR to WARN June 16, 2023 -> It's the effect of an already-known issue. Reduced visibility to clean up server logs while still making the issue apparent in dev environs + log(this.getClass(), Log.WARN, "Construction (building): " + hotspot + " : " + object + " chunkX = " + object.getLocation().getChunkX() + ", chunkY = " + object.getLocation().getChunkY()); return true; } @@ -181,4 +181,4 @@ public final class BuildOptionPlugin extends OptionHandler { } -} \ No newline at end of file +} diff --git a/Server/src/main/content/global/skill/crafting/LeatherCraftDialogue.java b/Server/src/main/content/global/skill/crafting/LeatherCraftDialogue.java index 7096ab935..0692339a6 100644 --- a/Server/src/main/content/global/skill/crafting/LeatherCraftDialogue.java +++ b/Server/src/main/content/global/skill/crafting/LeatherCraftDialogue.java @@ -1,6 +1,7 @@ package content.global.skill.crafting; import static core.api.ContentAPIKt.*; +import core.api.InputType; import core.cache.def.impl.ItemDefinition; import core.game.component.Component; import core.game.dialogue.DialoguePlugin; @@ -204,7 +205,7 @@ public final class LeatherCraftDialogue extends DialoguePlugin { if (hidee == null) { return false; } - sendInputDialogue(player, true, "Enter the amount:", (value) -> { + sendInputDialogue(player, InputType.AMOUNT, "Enter the amount:", (value) -> { player.getPulseManager().run(new DragonCraftPulse(player, null, hidee, (int) value)); return Unit.INSTANCE; }); diff --git a/Server/src/main/content/global/skill/farming/ToolLeprechaunInterface.kt b/Server/src/main/content/global/skill/farming/ToolLeprechaunInterface.kt index 175c2af71..9ef9aa7a7 100644 --- a/Server/src/main/content/global/skill/farming/ToolLeprechaunInterface.kt +++ b/Server/src/main/content/global/skill/farming/ToolLeprechaunInterface.kt @@ -181,7 +181,7 @@ class ToolLeprechaunInterface : InterfaceListener { player.dialogueInterpreter.sendDialogue("You don't have any of those stored.") } else { if(amount == -2){ - sendInputDialogue(player, true, "Enter the amount:"){value -> + sendInputDialogue(player, InputType.AMOUNT, "Enter the amount:"){value -> var amt = value as Int if(amt > hasAmount){ amt = hasAmount diff --git a/Server/src/main/content/global/skill/hunter/HunterPlugin.java b/Server/src/main/content/global/skill/hunter/HunterPlugin.java index 83cb8993b..b434c2287 100644 --- a/Server/src/main/content/global/skill/hunter/HunterPlugin.java +++ b/Server/src/main/content/global/skill/hunter/HunterPlugin.java @@ -303,7 +303,13 @@ public final class HunterPlugin extends OptionHandler { @Override public boolean handle(Player player, Node node, String option) { - BNetTypes.forNpc((NPC) node).handle(player, (NPC) node); + BNetTypes type = BNetTypes.forNpc((NPC) node); + if (type == null) { + player.sendMessage("There seems to be something wrong with this catch option."); + player.sendMessage("Please submit a detailed bug report on gitlab."); + return true; + } + type.handle(player, (NPC) node); return true; } diff --git a/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt b/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt index dbbc52752..d6a67dd90 100644 --- a/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt +++ b/Server/src/main/content/global/skill/magic/lunar/LunarListeners.kt @@ -387,6 +387,8 @@ class LunarListeners : SpellListener("lunar"), Commands { var counter = 0 override fun pulse(): Boolean { removeAttribute(player, "spell:runes") + if (playerJewellery.isEmpty()) + return true requires(player, 80, arrayOf(Item(Items.ASTRAL_RUNE_9075, 2), Item(Items.EARTH_RUNE_557, 10), Item(Items.WATER_RUNE_555, 5))) if(counter == 0) delay = animationDuration(STRING_JEWELLERY_ANIM) + 1 val item = playerJewellery[0] diff --git a/Server/src/main/content/global/skill/magic/lunar/StatRestoreSpell.java b/Server/src/main/content/global/skill/magic/lunar/StatRestoreSpell.java index 6fad08ad4..89d87f362 100644 --- a/Server/src/main/content/global/skill/magic/lunar/StatRestoreSpell.java +++ b/Server/src/main/content/global/skill/magic/lunar/StatRestoreSpell.java @@ -41,10 +41,17 @@ public class StatRestoreSpell extends MagicSpell { public boolean cast(Entity entity, Node target) { final Player player = ((Player) entity); Item item = ((Item) target); - final Potion potion = (Potion) Consumables.getConsumableById(item.getId()).getConsumable(); player.getInterfaceManager().setViewedTab(6); + + if (Consumables.getConsumableById(item.getId()) == null) { + player.getPacketDispatch().sendMessage("You can only cast this spell on a potion."); + return false; + } + + final Potion potion = (Potion) Consumables.getConsumableById(item.getId()).getConsumable(); + if (potion == null) { - player.getPacketDispatch().sendMessage("You can only cast this spell on a potion."); + player.getPacketDispatch().sendMessage("You can only cast this spell on a potion."); return false; } if (!item.getDefinition().isTradeable() || !isRestore(potion)) { diff --git a/Server/src/main/content/global/skill/summoning/pet/IncubatorState.kt b/Server/src/main/content/global/skill/summoning/pet/IncubatorState.kt index 72ff9d086..dfe5adf8e 100644 --- a/Server/src/main/content/global/skill/summoning/pet/IncubatorState.kt +++ b/Server/src/main/content/global/skill/summoning/pet/IncubatorState.kt @@ -6,22 +6,27 @@ import core.game.node.entity.state.State import core.game.system.task.Pulse import org.json.simple.JSONObject import core.api.* +import core.tools.* @PlayerState("incubator") class IncubatorState(player: Player? = null) : State(player) { var egg: IncubatorEgg? = null - var ticksLeft = 0 + var completionTimeMs = 0L + + fun setTicksLeft (ticks: Int) { + completionTimeMs = System.currentTimeMillis() + (ticksToSeconds(ticks) * 1000) + } override fun newInstance(player: Player?): State { return IncubatorState(player) } override fun save(root: JSONObject) { - if(ticksLeft <= 0) return + if(pulse == null) return val data = JSONObject() data.put("eggOrdinal",(egg?.ordinal ?: 0).toString()) - data.put("ticksLeft",ticksLeft.toString()) + data.put("endTime", completionTimeMs.toString()) root.put("eggdata",data) } @@ -29,7 +34,11 @@ class IncubatorState(player: Player? = null) : State(player) { if(_data.containsKey("eggdata")){ val data = _data["eggdata"] as JSONObject egg = IncubatorEgg.values()[data["eggOrdinal"].toString().toInt()] - ticksLeft = data["ticksLeft"].toString().toInt() + + if (data.containsKey("ticksLeft")) + completionTimeMs = System.currentTimeMillis() + ticksToSeconds(data["ticksLeft"].toString().toInt() * 1000) + else + completionTimeMs = data["endTime"].toString().toLong() } } @@ -39,8 +48,8 @@ class IncubatorState(player: Player? = null) : State(player) { setVarbit(player, 4277, 1) pulse = object : Pulse(){ override fun pulse(): Boolean { - if(ticksLeft-- <= 0){ - player.setAttribute("/save:inc", egg!!.ordinal) + if(System.currentTimeMillis() >= completionTimeMs){ + player.setAttribute(ATTR_INCUBATOR_PRODUCT, egg!!.ordinal) player.sendMessage("Your " + egg!!.product.name.toLowerCase() + " has finished hatching.") pulse = null return true @@ -49,5 +58,8 @@ class IncubatorState(player: Player? = null) : State(player) { } } } - + + companion object { + const val ATTR_INCUBATOR_PRODUCT = "/save:incubator:egg-product" + } } diff --git a/Server/src/main/content/minigame/mta/TelekineticGrabSpell.java b/Server/src/main/content/minigame/mta/TelekineticGrabSpell.java index 7e1ac3530..9cb25850c 100644 --- a/Server/src/main/content/minigame/mta/TelekineticGrabSpell.java +++ b/Server/src/main/content/minigame/mta/TelekineticGrabSpell.java @@ -96,6 +96,8 @@ public final class TelekineticGrabSpell extends MagicSpell { @Override public boolean cast(final Entity entity, final Node target) { + if (!(target instanceof GroundItem)) + return false; final GroundItem ground = (GroundItem) target; if (!canCast(entity, ground)) { return false; diff --git a/Server/src/main/content/minigame/pyramidplunder/PlunderUtils.kt b/Server/src/main/content/minigame/pyramidplunder/PlunderUtils.kt index ac25834f2..28fbb6400 100644 --- a/Server/src/main/content/minigame/pyramidplunder/PlunderUtils.kt +++ b/Server/src/main/content/minigame/pyramidplunder/PlunderUtils.kt @@ -171,6 +171,8 @@ object PlunderUtils { fun getDoor(player: Player) : Int { + if (getRoom(player) == null) + return -1; val room = getRoom(player)!!.room return PlunderData.doors[room - 1] } @@ -219,6 +221,10 @@ object PlunderUtils { fun getChestXp(player: Player): Double { + if (getRoom(player) == null) { + expel(player, false) + return 0.0 + } val room = getRoom(player)!!.room return when(room) { diff --git a/Server/src/main/content/minigame/pyramidplunder/PyramidPlunderMinigame.kt b/Server/src/main/content/minigame/pyramidplunder/PyramidPlunderMinigame.kt index e2c7ba0d4..7f8d72b37 100644 --- a/Server/src/main/content/minigame/pyramidplunder/PyramidPlunderMinigame.kt +++ b/Server/src/main/content/minigame/pyramidplunder/PyramidPlunderMinigame.kt @@ -188,6 +188,10 @@ class PyramidPlunderMinigame : InteractionListener, TickListener, LogoutListener } on(SARCOPHAGUS, IntType.SCENERY, "open") { player, node -> + if (PlunderUtils.getRoom(player) == null) { + PlunderUtils.expel(player, false) + return@on true + } sendMessage(player, "You attempt to push open the massive lid.") val strength = getDynLevel(player, Skills.STRENGTH) animate(player, PUSH_LID_START) @@ -198,6 +202,10 @@ class PyramidPlunderMinigame : InteractionListener, TickListener, LogoutListener if(RandomFunction.random(125) > strength) return false animate(player, PUSH_LID_FINISH) + if (PlunderUtils.getRoom(player) == null) { + PlunderUtils.expel(player, false) + return true + } runTask(player, 3){ setVarbit(player, node.asScenery().definition.varbitID, 1) rewardXP(player, Skills.STRENGTH, PlunderUtils.getSarcophagusXp(player)) @@ -220,6 +228,10 @@ class PyramidPlunderMinigame : InteractionListener, TickListener, LogoutListener } on(Scenery.GRAND_GOLD_CHEST_16473, IntType.SCENERY, "search") { player, node -> + if (PlunderUtils.getRoom(player) == null) { + PlunderUtils.expel(player, false) + return@on true + } animate(player, OPEN_CHEST_ANIM) runTask(player){ if(RandomFunction.roll(25)) @@ -268,8 +280,14 @@ class PyramidPlunderMinigame : InteractionListener, TickListener, LogoutListener if(RandomFunction.roll(rate)) { val varbitId = node.asScenery().definition.varbitID - rewardXP(player, Skills.THIEVING, PlunderUtils.getDoorXp(player, rate == 3)) - if(PlunderUtils.getDoor(player) == varbitId) { + val door = PlunderUtils.getDoor(player) + + if (door == -1) + PlunderUtils.expel(player, false) + else + rewardXP(player, Skills.THIEVING, PlunderUtils.getDoorXp(player, rate == 3)) + + if (door == varbitId) { PlunderUtils.loadNextRoom(player) PlunderUtils.resetObjectVarbits(player) } else { diff --git a/Server/src/main/content/minigame/vinesweeper/VinesweeperDialogues.kt b/Server/src/main/content/minigame/vinesweeper/VinesweeperDialogues.kt index f51a18bde..23c012487 100644 --- a/Server/src/main/content/minigame/vinesweeper/VinesweeperDialogues.kt +++ b/Server/src/main/content/minigame/vinesweeper/VinesweeperDialogues.kt @@ -4,6 +4,7 @@ import core.game.node.item.Item import org.rs09.consts.Items import core.game.dialogue.DialogueFile import core.tools.END_DIALOGUE +import core.api.* abstract class FarmerDialogue : DialogueFile() { @@ -129,10 +130,11 @@ class BlinkinDialogue : FarmerDialogue() { 40 -> playerl("Do you have any Ogleroots to feed the rabbits?").also { stage++ } 41 -> npcl("I sure do. They'll cost ya 10 gold each. Any ya leave with will be returned to us, but ya'll get yer money back for 'em. How many do ya want?").also { stage++ } 42 -> { - player!!.setAttribute("runscript") { amount: Int -> + sendInputDialogue(player!!, InputType.AMOUNT, "Enter an amount:") { value -> + val amount = value as Int val price = Item(Items.COINS_995, 10 * amount) if(price.amount <= 0){ - return@setAttribute + return@sendInputDialogue } if(player!!.inventory.containsItem(price) && player!!.inventory.remove(price)) { if(player!!.inventory.add(Item(Items.OGLEROOT_12624, amount))) { diff --git a/Server/src/main/content/region/desert/handlers/KalphiteQueenNPC.java b/Server/src/main/content/region/desert/handlers/KalphiteQueenNPC.java index 5a4e59557..cff2ae212 100644 --- a/Server/src/main/content/region/desert/handlers/KalphiteQueenNPC.java +++ b/Server/src/main/content/region/desert/handlers/KalphiteQueenNPC.java @@ -315,7 +315,7 @@ public final class KalphiteQueenNPC extends AbstractNPC { return; } if (style == CombatStyle.MAGIC) { - BattleState[] states = new BattleState[entity.getViewport().getCurrentPlane().getPlayers().size()]; + BattleState[] states = new BattleState[entity.getViewport().getCurrentPlane().getPlayers().size() + 1]; int index = 1; if (states.length == 0) { return; diff --git a/Server/src/main/content/region/desert/quest/thetouristrap/BedabinNomadDialogue.java b/Server/src/main/content/region/desert/quest/thetouristrap/BedabinNomadDialogue.java index e4a1b05d6..8cdabc88d 100644 --- a/Server/src/main/content/region/desert/quest/thetouristrap/BedabinNomadDialogue.java +++ b/Server/src/main/content/region/desert/quest/thetouristrap/BedabinNomadDialogue.java @@ -94,7 +94,8 @@ public final class BedabinNomadDialogue extends DialoguePlugin { case 2: end(); final Scenery door = RegionManager.getObject(new Location(3169, 3046, 0)); - SceneryBuilder.replace(door, door.transform(2701), 2); + if (door.getId() != 2701) + SceneryBuilder.replace(door, door.transform(2701), 2); player.getWalkingQueue().reset(); player.getWalkingQueue().addPath(3169, 3046); player.getPacketDispatch().sendMessage("You walk into the tent."); diff --git a/Server/src/main/content/region/kandarin/ardougne/quest/arena/cutscenes/EscapeCutscene.kt b/Server/src/main/content/region/kandarin/ardougne/quest/arena/cutscenes/EscapeCutscene.kt index 39caceb0c..fe4ead691 100644 --- a/Server/src/main/content/region/kandarin/ardougne/quest/arena/cutscenes/EscapeCutscene.kt +++ b/Server/src/main/content/region/kandarin/ardougne/quest/arena/cutscenes/EscapeCutscene.kt @@ -27,7 +27,7 @@ class EscapeCutscene(player: Player) : Cutscene(player) { } 1 -> { - player.faceLocation(location(2616, 3167, 0)) + player.faceLocation(location(56, 31, 0)) animate(player, 2098) timedUpdate(4) } @@ -38,8 +38,8 @@ class EscapeCutscene(player: Player) : Cutscene(player) { } 3 -> { - DoorActionHandler.handleAutowalkDoor(Jeremy, getScenery(2617, 3167, 0)) - player.faceLocation(location(2617, 3164, 0)) + DoorActionHandler.handleAutowalkDoor(Jeremy, getObject(57, 31, 0)) + player.faceLocation(location(57, 28, 0)) timedUpdate(3) } @@ -67,7 +67,7 @@ class EscapeCutscene(player: Player) : Cutscene(player) { moveCamera(47, 20) rotateCamera(45, 15) teleport(player, 47, 15) - Jeremy.teleport(Location.create(2616, 3167, 0)) + Jeremy.teleport(location(56, 31, 0)) timedUpdate(2) } diff --git a/Server/src/main/content/region/kandarin/feldip/gutanoth/handlers/BogrogPlugin.java b/Server/src/main/content/region/kandarin/feldip/gutanoth/handlers/BogrogPlugin.java index 691ee67f3..c66f8e3ae 100644 --- a/Server/src/main/content/region/kandarin/feldip/gutanoth/handlers/BogrogPlugin.java +++ b/Server/src/main/content/region/kandarin/feldip/gutanoth/handlers/BogrogPlugin.java @@ -12,6 +12,9 @@ import core.game.node.entity.npc.NPC; import core.game.node.entity.player.Player; import core.plugin.Plugin; import core.plugin.ClassScanner; +import kotlin.Unit; + +import static core.api.ContentAPIKt.*; /** * Handles the bogrog npc. @@ -46,8 +49,10 @@ public final class BogrogPlugin extends OptionHandler { player.sendMessage("You need a Summoning level of at least 21 in order to do that."); return; } else { - InterfaceContainer.generateItems(player,player.getInventory().toArray(),new String[] {"Swap X","Swap 10","Swap 5","Swap 1","Value"}, 644,0,7,4,200); - player.getInterfaceManager().openSingleTab(new Component(644)); + sendItemSelect(player, new String[] { "Value", "Swap 1", "Swap 5", "Swap 10", "Swap X" }, true, (slot, index) -> { + BogrogPouchSwapper.handle(player, index, slot); + return Unit.INSTANCE; + }); } } diff --git a/Server/src/main/content/region/kandarin/feldip/gutanoth/handlers/BogrogPouchSwapper.kt b/Server/src/main/content/region/kandarin/feldip/gutanoth/handlers/BogrogPouchSwapper.kt index 8af239407..02481d4c5 100644 --- a/Server/src/main/content/region/kandarin/feldip/gutanoth/handlers/BogrogPouchSwapper.kt +++ b/Server/src/main/content/region/kandarin/feldip/gutanoth/handlers/BogrogPouchSwapper.kt @@ -17,11 +17,11 @@ import kotlin.math.floor */ object BogrogPouchSwapper { //Opcodes for item options - private const val OP_VALUE = 155 - private const val OP_SWAP_1 = 196 - private const val OP_SWAP_5 = 124 - private const val OP_SWAP_10 = 199 - private const val OP_SWAP_X = 234 + private const val OP_VALUE = 0 + private const val OP_SWAP_1 = 1 + private const val OP_SWAP_5 = 2 + private const val OP_SWAP_10 = 3 + private const val OP_SWAP_X = 4 private const val SPIRIT_SHARD = 12183 @@ -29,16 +29,16 @@ object BogrogPouchSwapper { private val GEBorders = ZoneBorders(3151,3501,3175,3477) @JvmStatic - fun handle(player: Player, opcode: Int, slot: Int, itemID: Int): Boolean{ - if(GEBorders.insideBorder(player)) return false - return when(opcode){ - OP_VALUE -> sendValue(player.inventory.get(slot).id,player) - OP_SWAP_1 -> swap(player, 1, player.inventory.get(slot).id) - OP_SWAP_5 -> swap(player, 5, player.inventory.get(slot).id) - OP_SWAP_10 -> swap(player,10,player.inventory.get(slot).id) + fun handle(player: Player, optionIndex: Int, slot: Int): Boolean{ + val item = player.inventory.get(slot) ?: return false + return when(optionIndex){ + OP_VALUE -> sendValue(item.id,player) + OP_SWAP_1 -> swap(player, 1, item.id) + OP_SWAP_5 -> swap(player, 5, item.id) + OP_SWAP_10 -> swap(player, 10, item.id) OP_SWAP_X -> true.also{ - sendInputDialogue(player, true, "Enter the amount:"){value -> - swap(player, value as Int, player.inventory.get(slot).id) + sendInputDialogue(player, InputType.AMOUNT, "Enter the amount:"){value -> + swap(player, value as Int, item.id) } } else -> false @@ -56,7 +56,6 @@ object BogrogPouchSwapper { amt = inInventory player.inventory.remove(Item(itemID,amt)) player.inventory.add(Item(SPIRIT_SHARD, floor(value * amt).toInt())) - player.interfaceManager.close(Component(644)) return true } @@ -66,7 +65,7 @@ object BogrogPouchSwapper { return false } - player.sendMessage("Bogrog will give you $value shards for that.") + player.sendMessage("Bogrog will give you ${floor(value).toInt()} shards for that.") return true } @@ -80,4 +79,4 @@ object BogrogPouchSwapper { if(isScroll) shardQuantity /= 20.0 return shardQuantity } -} \ No newline at end of file +} diff --git a/Server/src/main/content/region/kandarin/quest/fishingcontest/StairInteraction.java b/Server/src/main/content/region/kandarin/quest/fishingcontest/StairInteraction.java index 60b103359..c4ca0001b 100644 --- a/Server/src/main/content/region/kandarin/quest/fishingcontest/StairInteraction.java +++ b/Server/src/main/content/region/kandarin/quest/fishingcontest/StairInteraction.java @@ -10,6 +10,7 @@ import core.plugin.Initializable; import core.plugin.Plugin; import core.game.interaction.PluginInteraction; import core.game.interaction.PluginInteractionManager; +import core.game.world.repository.Repository; @Initializable public class StairInteraction extends PluginInteraction { @@ -33,7 +34,12 @@ public class StairInteraction extends PluginInteraction { player.getPulseManager().run(new MovementPulse(player,object.getLocation().transform(0,2,0)) { @Override public boolean pulse() { - player.getDialogueInterpreter().open(npc_id,new NPC(npc_id)); + NPC n = Repository.findNPC(npc_id); + if (n == null) { + player.sendMessage("Are you in a world without NPCs? What did you do?"); + return true; + } + player.getDialogueInterpreter().open(npc_id, n); return true; } }, PulseType.STANDARD); diff --git a/Server/src/main/content/region/misthalin/varrock/handlers/ZaffPlugin.kt b/Server/src/main/content/region/misthalin/varrock/handlers/ZaffPlugin.kt index 64abda94f..09a6f5c4e 100644 --- a/Server/src/main/content/region/misthalin/varrock/handlers/ZaffPlugin.kt +++ b/Server/src/main/content/region/misthalin/varrock/handlers/ZaffPlugin.kt @@ -387,7 +387,7 @@ class ZaffPlugin : OptionHandler() { ) stage = 1 } - 1 -> end().also { sendInputDialogue(player, InputType.NUMERIC, "Enter an amount:"){ value -> + 1 -> end().also { sendInputDialogue(player, InputType.AMOUNT, "Enter an amount:"){ value -> ammount = getStoreFile().getInt(player.username.toLowerCase()) var amt = value as Int if(amt > maxStaffs - ammount) amt = maxStaffs - ammount diff --git a/Server/src/main/content/region/wilderness/handlers/CorporealBeastNPC.java b/Server/src/main/content/region/wilderness/handlers/CorporealBeastNPC.java index ffd6a0778..ea1849876 100644 --- a/Server/src/main/content/region/wilderness/handlers/CorporealBeastNPC.java +++ b/Server/src/main/content/region/wilderness/handlers/CorporealBeastNPC.java @@ -163,6 +163,8 @@ public final class CorporealBeastNPC extends NPCBehavior { GameWorld.getPulser().submit(new Pulse(2, corp) { @Override public boolean pulse() { + if (npc.darkEnergyCore == null) + return true; npc.darkEnergyCore.init(); return true; } diff --git a/Server/src/main/core/api/ContentAPI.kt b/Server/src/main/core/api/ContentAPI.kt index a7de58500..912822b8a 100644 --- a/Server/src/main/core/api/ContentAPI.kt +++ b/Server/src/main/core/api/ContentAPI.kt @@ -1543,6 +1543,7 @@ fun sendInputDialogue(player: Player, type: InputType, prompt: String, handler: } player.setAttribute("runscript", handler) + player.setAttribute("input-type", type) } /** @@ -1728,9 +1729,11 @@ fun runcs2 (player: Player, scriptId: Int, vararg arguments: Any) { * Opens a generic item selection prompt with a glowing background, with your own callback to handle the selection. * @param player the player we are openinig the prompt for * @param options the right-click options the items should have + * @param keepAlive whether or not the selection prompt should remain open for multiple interactions * @param callback a callback to handle the selection. The parameters passed to the callback are the slot in the inventory of the selected item, and the 0-9 index of the option clicked. **/ -fun sendItemSelect (player: Player, vararg options: String, callback: (slot: Int, optionIndex: Int) -> Unit) { +@JvmOverloads +fun sendItemSelect (player: Player, vararg options: String, keepAlive: Boolean = false, callback: (slot: Int, optionIndex: Int) -> Unit) { player.interfaceManager.openSingleTab(Component(12)) val scriptArgs = arrayOf ((12 shl 16) + 18, 93, 4, 7, 0, -1, "", "", "", "", "", "", "", "", "") for (i in 0 until kotlin.math.min(9, options.size)) @@ -1741,6 +1744,7 @@ fun sendItemSelect (player: Player, vararg options: String, callback: (slot: Int .build() player.packetDispatch.sendIfaceSettings(settings, 18, 12, 0, 28) setAttribute(player, "itemselect-callback", callback) + setAttribute(player, "itemselect-keepalive", keepAlive) } fun announceIfRare(player: Player, item: Item) { @@ -2545,14 +2549,22 @@ fun logWithStack(origin: Class<*>, type: Log, message: String) { try { throw Exception(message) } catch (e: Exception) { - val sw = StringWriter() - val pw = PrintWriter(sw) - e.printStackTrace(pw) - - log(origin, type, "$sw") + log(origin, type, "${exceptionToString(e)}") } } +/** + * Takes an exception as an argument, and sends back the complete exception with stack trace as a standard string. Useful for various things. + * @param e The exception you wish to convert. + * @return a string with the full stack trace of the exception +**/ +fun exceptionToString (e: Exception) : String { + val sw = StringWriter() + val pw = PrintWriter(sw) + e.printStackTrace(pw) + return sw.toString() +} + /** * Used by content handlers to check if the entity is done moving yet */ diff --git a/Server/src/main/core/api/utils/Exceptions.kt b/Server/src/main/core/api/utils/Exceptions.kt new file mode 100644 index 000000000..4dd71a813 --- /dev/null +++ b/Server/src/main/core/api/utils/Exceptions.kt @@ -0,0 +1,3 @@ +package core.api.utils + +class ConfigParseException (message: String) : Exception (message) diff --git a/Server/src/main/core/api/utils/WeightBasedTable.kt b/Server/src/main/core/api/utils/WeightBasedTable.kt index cd4eb3ca9..c3a0acc50 100644 --- a/Server/src/main/core/api/utils/WeightBasedTable.kt +++ b/Server/src/main/core/api/utils/WeightBasedTable.kt @@ -27,10 +27,26 @@ open class WeightBasedTable : ArrayList() { guaranteedItems.add(element) } else { totalWeight += element.weight + val randIndex = RandomFunction.random(0, size) + val end = this.size super.add(element) + + val temp = this[randIndex] + this[randIndex] = element + this[end] = temp + true } } + fun roll(receiver: Entity? = null, rollCount: Int) : ArrayList { + val items = ArrayList() + + for (i in 0 until rollCount) + items.addAll(roll(receiver)) + + return items + } + open fun roll(receiver: Entity? = null): ArrayList{ val items = ArrayList() items.addAll(guaranteedItems) @@ -40,7 +56,7 @@ open class WeightBasedTable : ArrayList() { } else if (isNotEmpty()) { var rngWeight = RandomFunction.randomDouble(totalWeight) - for (item in shuffled()) { + for (item in this) { rngWeight -= item.weight if (rngWeight <= 0) { items.add(item) @@ -179,4 +195,4 @@ open class WeightBasedTable : ArrayList() { } return builder.toString() } -} \ No newline at end of file +} diff --git a/Server/src/main/core/game/dialogue/DialogueFile.kt b/Server/src/main/core/game/dialogue/DialogueFile.kt index 37d3cea88..f81911e31 100644 --- a/Server/src/main/core/game/dialogue/DialogueFile.kt +++ b/Server/src/main/core/game/dialogue/DialogueFile.kt @@ -20,6 +20,7 @@ abstract class DialogueFile { this.player = player this.npc = npc this.interpreter = interpreter + interpreter.activeTopics.clear() return this } diff --git a/Server/src/main/core/game/dialogue/DialogueInterpreter.java b/Server/src/main/core/game/dialogue/DialogueInterpreter.java index 4974a39ad..67e285e8b 100644 --- a/Server/src/main/core/game/dialogue/DialogueInterpreter.java +++ b/Server/src/main/core/game/dialogue/DialogueInterpreter.java @@ -142,8 +142,9 @@ public final class DialogueInterpreter { close(); return; } + DialogueFile file = dialogue.file; - if (!activeTopics.isEmpty()) { + if (!activeTopics.isEmpty() && buttonId >= 2) { Topic topic = activeTopics.get(buttonId - 2); if (!topic.getSkipPlayer()) @@ -573,4 +574,4 @@ public final class DialogueInterpreter { public List getActions() { return actions; } -} \ No newline at end of file +} diff --git a/Server/src/main/core/game/dialogue/DialoguePlugin.java b/Server/src/main/core/game/dialogue/DialoguePlugin.java index e4967e2e9..635b41c2b 100644 --- a/Server/src/main/core/game/dialogue/DialoguePlugin.java +++ b/Server/src/main/core/game/dialogue/DialoguePlugin.java @@ -194,6 +194,7 @@ public abstract class DialoguePlugin implements Plugin { * @return {@code True} if the dialogue plugin succesfully opened. */ public boolean open(Object... args) { + player.getDialogueInterpreter().activeTopics.clear(); if (args.length > 0 && args[0] instanceof NPC) { npc = (NPC)args[0]; } diff --git a/Server/src/main/core/game/dialogue/SimpleEntityMessage.java b/Server/src/main/core/game/dialogue/SimpleEntityMessage.java deleted file mode 100644 index cfc718e91..000000000 --- a/Server/src/main/core/game/dialogue/SimpleEntityMessage.java +++ /dev/null @@ -1,46 +0,0 @@ -package core.game.dialogue; - -import core.plugin.Initializable; -import core.game.node.entity.player.Player; - -/** - * Handles the SimpleEntityMessage dialogue. - * @author 'Vexia - */ -@Initializable -public class SimpleEntityMessage extends DialoguePlugin { - - public SimpleEntityMessage() { - - } - - public SimpleEntityMessage(Player player) { - super(player); - } - - @Override - public int[] getIds() { - return new int[] { 8000 }; - } - - @Override - public boolean handle(int interfaceId, int buttonId) { - end(); - return true; - } - - @Override - public DialoguePlugin newInstance(Player player) { - - return new SimpleEntityMessage(player); - } - - @Override - public boolean open(Object... args) { - String[] messages = new String[args.length]; - for (int i = 0; i < messages.length; i++) - messages[i] = (String) args[i]; - interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, messages); - return true; - } -} diff --git a/Server/src/main/core/game/global/report/AbuseReport.java b/Server/src/main/core/game/global/report/AbuseReport.java index 0a47d41f1..dee08a06f 100644 --- a/Server/src/main/core/game/global/report/AbuseReport.java +++ b/Server/src/main/core/game/global/report/AbuseReport.java @@ -51,7 +51,7 @@ public final class AbuseReport { CommandMapping.INSTANCE.get("mute").attemptHandling(player, new String[] {"mute", victim, "48h"}); } player.getPacketDispatch().sendMessage("Thank-you, your abuse report has been received."); - Discord.INSTANCE.postPlayerAlert(victim, "Abuse Report - " + rule.name()); + Discord.postPlayerAlert(victim, "Abuse Report - " + rule.name()); } /** diff --git a/Server/src/main/core/game/interaction/MovementPulse.java b/Server/src/main/core/game/interaction/MovementPulse.java index fb4adbfe2..628695b44 100644 --- a/Server/src/main/core/game/interaction/MovementPulse.java +++ b/Server/src/main/core/game/interaction/MovementPulse.java @@ -267,7 +267,8 @@ public abstract class MovementPulse extends Pulse { } else if(overrideMethod != null){ loc = overrideMethod.invoke(mover,destination); - if(loc == destination.getLocation()) loc = destinationFlag.getDestination(mover,destination); + if(loc == destination.getLocation() && destinationFlag != null) loc = destinationFlag.getDestination(mover,destination); + else if (loc == destination.getLocation()) loc = null; } if (loc == null) { diff --git a/Server/src/main/core/game/node/Node.java b/Server/src/main/core/game/node/Node.java index 4310044d2..fd20d6cda 100644 --- a/Server/src/main/core/game/node/Node.java +++ b/Server/src/main/core/game/node/Node.java @@ -197,6 +197,8 @@ public abstract class Node { * @param direction The direction to set. */ public void setDirection(Direction direction) { + if (direction == null) + return; this.direction = direction; } diff --git a/Server/src/main/core/game/node/entity/npc/drop/NPCDropTables.java b/Server/src/main/core/game/node/entity/npc/drop/NPCDropTables.java index f133408db..4a676150d 100644 --- a/Server/src/main/core/game/node/entity/npc/drop/NPCDropTables.java +++ b/Server/src/main/core/game/node/entity/npc/drop/NPCDropTables.java @@ -42,7 +42,7 @@ public final class NPCDropTables { */ public static final int[] MESSAGE_NPCS = { 50, 7133, 7134, 2881, 2882, 2883, 3200, 3340, 6247, 6203, 6260, 6222, 2745, 1160, 8133, 8610, 8611, 8612, 8613, 8614, 6204, 6206, 6208, 6261, 6263, 6265, 6223, 6225, 6227 }; - public final NPCDropTable table = new NPCDropTable(); + public NPCDropTable table = new NPCDropTable(); /** * The NPC definitions. 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 78055a720..15b6e9e82 100644 --- a/Server/src/main/core/game/node/entity/player/Player.java +++ b/Server/src/main/core/game/node/entity/player/Player.java @@ -317,6 +317,8 @@ public class Player extends Entity { public byte[] opCounts = new byte[255]; + public int invalidPacketCount = 0; + /** * Constructs a new {@code Player} {@code Object}. * @param details The player's details. @@ -1386,4 +1388,12 @@ public class Player extends Entity { public void updateAppearance() { getUpdateMasks().register(EntityFlag.Appearance, this); } + + public void incrementInvalidPacketCount() { + invalidPacketCount++; + if (invalidPacketCount >= 5) { + clear(); + log(this.getClass(), Log.ERR, "Disconnecting " + getName() + " for having a high rate of invalid packets. Potential packet bot misbehaving, or simply really bad connection."); + } + } } 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 db9e3d1f0..7127babf3 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 @@ -643,6 +643,8 @@ class PlayerSaver (val player: Player){ val varpData = JSONArray() for ((index, value) in player.varpMap) { + if (!(player.saveVarp[index] ?: false)) continue + val varpObj = JSONObject() varpObj["index"] = index.toString() varpObj["value"] = value.toString() 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 a1699352f..dcf8fb7db 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 @@ -570,7 +570,7 @@ public final class InterfaceManager { */ public void setViewedTab(int tabIndex) { if (tabs[tabIndex] == null) { - throw new IllegalStateException("Tab at index " + tabIndex + " is null!"); + return; } currentTabIndex = tabIndex; switch (tabIndex) { diff --git a/Server/src/main/core/game/node/entity/state/StateRepository.kt b/Server/src/main/core/game/node/entity/state/StateRepository.kt index 9f6419153..2cc7fc3b3 100644 --- a/Server/src/main/core/game/node/entity/state/StateRepository.kt +++ b/Server/src/main/core/game/node/entity/state/StateRepository.kt @@ -27,7 +27,7 @@ class StateRepository : StartupListener{ @JvmStatic fun forKey(key: String, player: Player): State?{ val state = states[key] - if(player.states[key] != null){ + if(player.hasActiveState(key)){ return null } if(state != null){ @@ -38,4 +38,4 @@ class StateRepository : StartupListener{ return null } } -} \ No newline at end of file +} diff --git a/Server/src/main/core/game/node/scenery/SceneryBuilder.java b/Server/src/main/core/game/node/scenery/SceneryBuilder.java index 63b7bd8ed..3dac14bf0 100644 --- a/Server/src/main/core/game/node/scenery/SceneryBuilder.java +++ b/Server/src/main/core/game/node/scenery/SceneryBuilder.java @@ -46,7 +46,6 @@ public final class SceneryBuilder { remove = remove.getWrapper(); Scenery current = LandscapeParser.removeScenery(remove); if (current == null) { - log(SceneryBuilder.class, Log.ERR, "Object could not be replaced with " + construct + " - object to remove is invalid."); return false; } if (current.getRestorePulse() != null) { @@ -119,7 +118,6 @@ public final class SceneryBuilder { remove = remove.getWrapper(); Scenery current = LandscapeParser.removeScenery(remove); if (current == null) { - log(SceneryBuilder.class, Log.ERR, "Object could not be replaced with " + construct + " - object to remove is invalid."); return false; } if (current.getRestorePulse() != null) { diff --git a/Server/src/main/core/game/shops/Shop.kt b/Server/src/main/core/game/shops/Shop.kt index 3a22e35b0..ac2bb7b2b 100644 --- a/Server/src/main/core/game/shops/Shop.kt +++ b/Server/src/main/core/game/shops/Shop.kt @@ -158,6 +158,12 @@ class Shop(val title: String, val stock: Array, val general: Boolean = val isMainStock = getAttribute(player, "shop-main", true) val cont = if (isMainStock) getAttribute(player, "shop-cont", null) ?: return Item(-1,-1) else playerStock val item = cont[slot] + + if (item == null) { + player.sendMessage("That item doesn't appear to be there anymore. Please try again.") + return Item(-1, -1) + } + val price = when(currency) { Items.TOKKUL_6529 -> item.definition.getConfiguration(ItemConfigParser.TOKKUL_PRICE, 1) @@ -173,6 +179,13 @@ class Shop(val title: String, val stock: Array, val general: Boolean = { val shopCont = getAttribute(player, "shop-cont", null) ?: return Pair(null, Item(-1,-1)) val item = player.inventory[slot] + + if (item == null) { + player.debug("Inventory slot $slot does not contain an item!") + player.sendMessage("That item doesn't seem to be there anymore. Please try again.") + return Pair(null, Item(-1,-1)) + } + val shopItemId = if (item.definition.isUnnoted) { item.id } diff --git a/Server/src/main/core/game/shops/Shops.kt b/Server/src/main/core/game/shops/Shops.kt index 52a8de020..3b5697306 100644 --- a/Server/src/main/core/game/shops/Shops.kt +++ b/Server/src/main/core/game/shops/Shops.kt @@ -184,7 +184,7 @@ class Shops : StartupListener, TickListener, InteractionListener, InterfaceListe OP_BUY_1 -> shop.buy(player, slot, 1) OP_BUY_5 -> shop.buy(player, slot, 5) OP_BUY_10 -> shop.buy(player, slot, 10) - OP_BUY_X -> sendInputDialogue(player, true, "Enter the amount to buy:"){value -> + OP_BUY_X -> sendInputDialogue(player, InputType.AMOUNT, "Enter the amount to buy:"){value -> val amt = value as Int shop.buy(player, slot, amt) } @@ -226,6 +226,12 @@ class Shops : StartupListener, TickListener, InteractionListener, InterfaceListe val shop = getAttribute(player, "shop", null) ?: return@on false + val itemInSlot = player.inventory[slot] + if (itemInSlot == null) { + player.sendMessage("That item doesn't appear to be there anymore. Please try again.") + return@on true + } + val (_,price) = shop.getSellPrice(player, slot) val def = itemDefinition(player.inventory[slot].id) @@ -243,7 +249,7 @@ class Shops : StartupListener, TickListener, InteractionListener, InterfaceListe OP_SELL_1 -> shop.sell(player, slot, 1) OP_SELL_5 -> shop.sell(player, slot, 5) OP_SELL_10 -> shop.sell(player, slot, 10) - OP_SELL_X -> sendInputDialogue(player, true, "Enter the amount to sell:"){value -> + OP_SELL_X -> sendInputDialogue(player, InputType.AMOUNT, "Enter the amount to sell:"){value -> val amt = value as Int shop.sell(player, slot, amt) } diff --git a/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt b/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt index 5532c7d72..c193f5c64 100644 --- a/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt @@ -258,6 +258,10 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){ reject(player, "Usage: ::commands page") } + if (page > pages.size) { + reject(player, "That page number is too high, you don't have access to that many.") + } + for (i in 0..310) { player.packetDispatch.sendString("", Components.QUESTJOURNAL_SCROLL_275, i) } diff --git a/Server/src/main/core/game/system/command/sets/MusicCommandSet.kt b/Server/src/main/core/game/system/command/sets/MusicCommandSet.kt index 09e99cf03..def057ecb 100644 --- a/Server/src/main/core/game/system/command/sets/MusicCommandSet.kt +++ b/Server/src/main/core/game/system/command/sets/MusicCommandSet.kt @@ -46,6 +46,8 @@ class MusicCommandSet : CommandSet(Privilege.STANDARD){ * music player yet. */ define("playid"){player,arg -> + if (arg.size < 2) + reject(player, "Needs more args.") val id = arg[1].toIntOrNull() if(id != null){ PacketRepository.send(MusicPacket::class.java, MusicContext(player, id)) @@ -63,4 +65,4 @@ class MusicCommandSet : CommandSet(Privilege.STANDARD){ } } } -} \ No newline at end of file +} diff --git a/Server/src/main/core/game/system/communication/CommunicationInfo.java b/Server/src/main/core/game/system/communication/CommunicationInfo.java index ea146fec3..895d45865 100644 --- a/Server/src/main/core/game/system/communication/CommunicationInfo.java +++ b/Server/src/main/core/game/system/communication/CommunicationInfo.java @@ -257,6 +257,10 @@ public final class CommunicationInfo { */ public static void add(Player player, String contact) { CommunicationInfo info = player.getDetails().getCommunication(); + if (contact.isEmpty()) { + player.sendMessage("Unable to find a player by that name. Please try again."); + return; + } if (WorldCommunicator.isEnabled()) { MSPacketRepository.sendContactUpdate(player.getName(), contact, false, false, null); return; @@ -291,6 +295,10 @@ public final class CommunicationInfo { * @param block If the contact should be removed from the block list. */ public static void remove(Player player, String contact, boolean block) { + if (contact.isEmpty()) { + player.sendMessage("Unable to find a player by that name. Please try again."); + return; + } if (WorldCommunicator.isEnabled()) { MSPacketRepository.sendContactUpdate(player.getName(), contact, true, block, null); return; @@ -326,6 +334,10 @@ public final class CommunicationInfo { * @param contact The contact to block. */ public static void block(Player player, String contact) { + if (contact.isEmpty()) { + player.sendMessage("Unable to find a player by that name. Please try again."); + return; + } if (WorldCommunicator.isEnabled()) { MSPacketRepository.sendContactUpdate(player.getName(), contact, false, true, null); return; @@ -352,6 +364,10 @@ public final class CommunicationInfo { * @param clanRank The clan rank to set. */ public static void updateClanRank(Player player, String contact, ClanRank clanRank) { + if (contact.isEmpty()) { + player.sendMessage("Unable to find a player by that name. Please try again."); + return; + } CommunicationInfo info = player.getDetails().getCommunication(); Contact c = info.contacts.get(contact); if (c == null) { diff --git a/Server/src/main/core/game/system/config/DropTableParser.kt b/Server/src/main/core/game/system/config/DropTableParser.kt index 4dc3b6793..30c1b6e2f 100644 --- a/Server/src/main/core/game/system/config/DropTableParser.kt +++ b/Server/src/main/core/game/system/config/DropTableParser.kt @@ -5,13 +5,13 @@ import org.json.simple.JSONArray import org.json.simple.JSONObject import org.json.simple.parser.JSONParser import core.ServerConstants -import core.api.log -import core.api.utils.NPCDropTable -import core.api.utils.WeightedItem +import core.api.* +import core.api.utils.* import core.tools.Log import core.tools.SystemLogger import java.io.FileReader + class DropTableParser { val parser = JSONParser() var reader: FileReader? = null @@ -22,13 +22,21 @@ class DropTableParser { for(i in obj){ val tab = i as JSONObject val ids = tab["ids"].toString().split(",") - for(n in ids){ - val def = NPCDefinition.forId(n.toInt()).dropTables - def ?: continue - parseTable(tab["main"] as JSONArray, def.table, false) - parseTable(tab["charm"] as JSONArray, def.table, false, true) - parseTable(tab["default"] as JSONArray,def.table,true) - count++ + + try { + val table = NPCDropTable() + parseTable(tab["main"] as JSONArray, table, false) + parseTable(tab["charm"] as JSONArray, table, false, true) + parseTable(tab["default"] as JSONArray, table, true) + + for(n in ids){ + val def = NPCDefinition.forId(n.toInt()).dropTables + def ?: continue + def.table = table + count++ + } + } catch (e: ConfigParseException) { + log (this::class.java, Log.ERR, "Error parsing drop tables for NPC ${NPCDefinition.forId(ids[0].toInt()).name}: ${exceptionToString(e)}") } } log(this::class.java, Log.FINE, "Parsed $count drop tables.") @@ -40,10 +48,15 @@ class DropTableParser { val id = item["id"].toString().toInt() val minAmount = item["minAmount"].toString().toInt() val maxAmount = item["maxAmount"].toString().toInt() + + if (minAmount > maxAmount) { + throw ConfigParseException("Table is invalid! Specified minimum amount is > specified maximum amount.") + } + val weight = item["weight"].toString().toDouble() val newItem = WeightedItem(id,minAmount,maxAmount,weight.toDouble(),isAlways) if(isCharms) destTable.addToCharms(newItem) else destTable.add(newItem) } } -} \ No newline at end of file +} diff --git a/Server/src/main/core/integrations/discord/Discord.kt b/Server/src/main/core/integrations/discord/Discord.kt index 77f841521..acb6755f7 100644 --- a/Server/src/main/core/integrations/discord/Discord.kt +++ b/Server/src/main/core/integrations/discord/Discord.kt @@ -1,6 +1,6 @@ package core.integrations.discord -import core.api.getItemName +import core.api.* import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import org.json.simple.JSONArray @@ -11,159 +11,193 @@ import java.io.DataOutputStream import java.io.InputStreamReader import java.net.HttpURLConnection import java.net.URL +import java.util.ArrayList -object Discord { - private const val COLOR_NEW_BUY_OFFER = 47789 - private const val COLOR_NEW_SALE_OFFER = 5752709 - private const val COLOR_OFFER_UPDATE = 15588691 +class Discord : TickListener { + override fun tick() { + if (!removeList.isEmpty()) { + pendingMessages.removeAll(removeList) + removeList.clear() + } + if (!pendingMessages.isEmpty() && !flushingPends) { + flushingPends = true + GlobalScope.launch { + try { + val count = pendingMessages.size + for (i in 0 until count) { + val (url, content) = pendingMessages[i] + sendJsonPost(url, content) + removeList.add(pendingMessages[i]) + } + } finally { + flushingPends = false + } + } + } + } - fun postNewOffer(isSale: Boolean, itemId: Int, value: Int, qty: Int, user: String) { - if (ServerConstants.DISCORD_GE_WEBHOOK.isEmpty()) return - GlobalScope.launch { - val offer = encodeOfferJson(isSale, itemId, value, qty, user) + companion object { + private const val COLOR_NEW_BUY_OFFER = 47789 + private const val COLOR_NEW_SALE_OFFER = 5752709 + private const val COLOR_OFFER_UPDATE = 15588691 + + private val pendingMessages = ArrayList>() + private var flushingPends = false + private val removeList = ArrayList>() + + fun postNewOffer(isSale: Boolean, itemId: Int, value: Int, qty: Int, user: String) { + if (ServerConstants.DISCORD_GE_WEBHOOK.isEmpty()) return + GlobalScope.launch { + val offer = encodeOfferJson(isSale, itemId, value, qty, user) + try { + sendJsonPost(ServerConstants.DISCORD_GE_WEBHOOK, offer) + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + fun postOfferUpdate(isSale: Boolean, itemId: Int, value: Int, amtLeft: Int) { + if (ServerConstants.DISCORD_GE_WEBHOOK.isEmpty()) return + GlobalScope.launch { + val offer = encodeUpdateJson(isSale, itemId, value, amtLeft) + try { + sendJsonPost(ServerConstants.DISCORD_GE_WEBHOOK, offer) + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + @JvmStatic + fun postPlayerAlert(player: String, type: String) { + if (ServerConstants.DISCORD_MOD_WEBHOOK.isEmpty()) return + GlobalScope.launch { + val alert = encodeUserAlert(type, player) + try { + sendJsonPost(ServerConstants.DISCORD_MOD_WEBHOOK, alert) + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + @JvmStatic fun sendToOpenRSC(player: String, type: String) { + if (ServerConstants.DISCORD_OPENRSC_HOOK.isEmpty()) return + GlobalScope.launch { + val alert = encodeUserAlert(type, player) + try { + sendJsonPost(ServerConstants.DISCORD_OPENRSC_HOOK, alert) + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + private fun encodeUpdateJson(sale: Boolean, itemId: Int, value: Int, amtLeft: Int): String { + val obj = JSONObject() + val embeds = JSONArray() + val embed = JSONObject() + + val fields = arrayOf( + EmbedField("Item", getItemName(itemId), false), + EmbedField("Amount Remaining", "%,d".format(amtLeft), true), + EmbedField("Price", "%,d".format(value) + "gp", true), + ) + + embed["title"] = if (sale) "Sell Offer Updated" else "Buy Offer Updated" + embed["color"] = COLOR_OFFER_UPDATE + embed["thumbnail"] = getItemImage(itemId) + embed["fields"] = getFields(fields) + + embeds.add(embed) + obj["embeds"] = embeds + + return obj.toJSONString() + } + + private fun encodeOfferJson(isSale: Boolean, itemId: Int, value: Int, qty: Int, user: String): String { + val obj = JSONObject() + val embeds = JSONArray() + val embed = JSONObject() + + val fields = arrayOf( + EmbedField("Player", user, false), + EmbedField("Item", getItemName(itemId), false), + EmbedField("Amount", "%,d".format(qty), true), + EmbedField("Price", "%,d".format(value) + "gp", true), + ) + + embed["title"] = if (isSale) "New Sell Offer" else "New Buy Offer" + embed["color"] = if (isSale) COLOR_NEW_SALE_OFFER else COLOR_NEW_BUY_OFFER + embed["thumbnail"] = getItemImage(itemId) + embed["fields"] = getFields(fields) + + embeds.add(embed) + obj["embeds"] = embeds + + return obj.toJSONString() + } + + private fun encodeUserAlert(type: String, player: String) : String { + val obj = JSONObject() + val embeds = JSONArray() + val embed = JSONObject() + + val fields = arrayOf( + EmbedField("Player", player, false), + EmbedField("Type", type, false) + ) + + embed["title"] = "Player Alert" + embed["fields"] = getFields(fields) + embeds.add(embed) + obj["embeds"] = embeds + return obj.toJSONString() + } + + private fun getFields(fields: Array): JSONArray { + val arr = JSONArray() + + for (field in fields) { + val o = JSONObject() + o["name"] = field.name + o["value"] = field.value + if (field.inline) o["inline"] = true + arr.add(o) + } + + return arr + } + + data class EmbedField(val name: String, val value: String, val inline: Boolean) + + fun getItemImage(id: Int) : JSONObject { + val obj = JSONObject() + obj["url"] = "https://github.com/2009scape/2009scape.github.io/raw/master/services/m%3Ddata/img/items/$id.png" + return obj + } + + private fun sendJsonPost(url: String = ServerConstants.DISCORD_GE_WEBHOOK, data: String) { try { - sendJsonPost(ServerConstants.DISCORD_GE_WEBHOOK, offer) + val conn = URL(url).openConnection() as HttpURLConnection + conn.doOutput = true + conn.requestMethod = "POST" + conn.setRequestProperty("Content-Type", "application/json") + conn.useCaches = false + + DataOutputStream(conn.outputStream).use { it.writeBytes(data) } + BufferedReader(InputStreamReader(conn.inputStream)).use { br -> + var line: String? + while (br.readLine().also { line = it } != null) { + println(line) + } + } } catch (e: Exception) { - e.printStackTrace() + pendingMessages.add(Pair(url, data)) } } } - - fun postOfferUpdate(isSale: Boolean, itemId: Int, value: Int, amtLeft: Int) { - if (ServerConstants.DISCORD_GE_WEBHOOK.isEmpty()) return - GlobalScope.launch { - val offer = encodeUpdateJson(isSale, itemId, value, amtLeft) - try { - sendJsonPost(ServerConstants.DISCORD_GE_WEBHOOK, offer) - } catch (e: Exception) { - e.printStackTrace() - } - } - } - - fun postPlayerAlert(player: String, type: String) { - if (ServerConstants.DISCORD_MOD_WEBHOOK.isEmpty()) return - GlobalScope.launch { - val alert = encodeUserAlert(type, player) - try { - sendJsonPost(ServerConstants.DISCORD_MOD_WEBHOOK, alert) - } catch (e: Exception) { - e.printStackTrace() - } - } - } - - @JvmStatic fun sendToOpenRSC(player: String, type: String) { - if (ServerConstants.DISCORD_OPENRSC_HOOK.isEmpty()) return - GlobalScope.launch { - val alert = encodeUserAlert(type, player) - try { - sendJsonPost(ServerConstants.DISCORD_OPENRSC_HOOK, alert) - } catch (e: Exception) { - e.printStackTrace() - } - } - } - - private fun encodeUpdateJson(sale: Boolean, itemId: Int, value: Int, amtLeft: Int): String { - val obj = JSONObject() - val embeds = JSONArray() - val embed = JSONObject() - - val fields = arrayOf( - EmbedField("Item", getItemName(itemId), false), - EmbedField("Amount Remaining", "%,d".format(amtLeft), true), - EmbedField("Price", "%,d".format(value) + "gp", true), - ) - - embed["title"] = if (sale) "Sell Offer Updated" else "Buy Offer Updated" - embed["color"] = COLOR_OFFER_UPDATE - embed["thumbnail"] = getItemImage(itemId) - embed["fields"] = getFields(fields) - - embeds.add(embed) - obj["embeds"] = embeds - - return obj.toJSONString() - } - - private fun encodeOfferJson(isSale: Boolean, itemId: Int, value: Int, qty: Int, user: String): String { - val obj = JSONObject() - val embeds = JSONArray() - val embed = JSONObject() - - val fields = arrayOf( - EmbedField("Player", user, false), - EmbedField("Item", getItemName(itemId), false), - EmbedField("Amount", "%,d".format(qty), true), - EmbedField("Price", "%,d".format(value) + "gp", true), - ) - - embed["title"] = if (isSale) "New Sell Offer" else "New Buy Offer" - embed["color"] = if (isSale) COLOR_NEW_SALE_OFFER else COLOR_NEW_BUY_OFFER - embed["thumbnail"] = getItemImage(itemId) - embed["fields"] = getFields(fields) - - embeds.add(embed) - obj["embeds"] = embeds - - return obj.toJSONString() - } - - private fun encodeUserAlert(type: String, player: String) : String { - val obj = JSONObject() - val embeds = JSONArray() - val embed = JSONObject() - - val fields = arrayOf( - EmbedField("Player", player, false), - EmbedField("Type", type, false) - ) - - embed["title"] = "Player Alert" - embed["fields"] = getFields(fields) - embeds.add(embed) - obj["embeds"] = embeds - return obj.toJSONString() - } - - private fun getFields(fields: Array): JSONArray { - val arr = JSONArray() - - for (field in fields) { - val o = JSONObject() - o["name"] = field.name - o["value"] = field.value - if (field.inline) o["inline"] = true - arr.add(o) - } - - return arr - } - - data class EmbedField(val name: String, val value: String, val inline: Boolean) - - fun getItemImage(id: Int) : JSONObject { - val obj = JSONObject() - obj["url"] = "https://github.com/2009scape/2009scape.github.io/raw/master/services/m%3Ddata/img/items/$id.png" - return obj - } - - private fun sendJsonPost(url: String = ServerConstants.DISCORD_GE_WEBHOOK, data: String) { - val conn = URL(url).openConnection() as HttpURLConnection - conn.doOutput = true - conn.requestMethod = "POST" - conn.setRequestProperty("Content-Type", "application/json") - conn.useCaches = false - - DataOutputStream(conn.outputStream).use { it.writeBytes(data) } - BufferedReader(InputStreamReader(conn.inputStream)).use { br -> - var line: String? - while (br.readLine().also { line = it } != null) { - println(line) - } - } - } -} \ No newline at end of file +} diff --git a/Server/src/main/core/net/amsc/MSPacketRepository.java b/Server/src/main/core/net/amsc/MSPacketRepository.java index c8ea619be..f1072e6fd 100644 --- a/Server/src/main/core/net/amsc/MSPacketRepository.java +++ b/Server/src/main/core/net/amsc/MSPacketRepository.java @@ -92,6 +92,9 @@ public final class MSPacketRepository { buffer.put(publicSetting); buffer.put(privateSetting); buffer.put(tradeSetting); - WorldCommunicator.getSession().write(buffer); + if (WorldCommunicator.getSession() != null) + WorldCommunicator.getSession().write(buffer); + else + player.sendMessage("Privacy settings unavailable at the moment. Please try again later."); } -} \ No newline at end of file +} diff --git a/Server/src/main/core/net/event/GameReadEvent.java b/Server/src/main/core/net/event/GameReadEvent.java index 0e5c061c4..f8dc8a948 100644 --- a/Server/src/main/core/net/event/GameReadEvent.java +++ b/Server/src/main/core/net/event/GameReadEvent.java @@ -74,6 +74,7 @@ public final class GameReadEvent extends IoReadEvent { size = getPacketSize(buffer, opcode, header, last); } if (size == -1) { + session.getPlayer().incrementInvalidPacketCount(); break; } if (buffer.remaining() < size) { @@ -136,11 +137,7 @@ public final class GameReadEvent extends IoReadEvent { } return buffer.getShort() & 0xFFFF; } - if(header == -3){ - - } - log(this.getClass(), Log.ERR, "Invalid packet [opcode=" + opcode + ", last=" + last + ", queued=" + usedQueuedBuffer + "], header=" + header+"!"); return -1; } -} \ No newline at end of file +} diff --git a/Server/src/main/core/net/event/MSReadEvent.java b/Server/src/main/core/net/event/MSReadEvent.java index 5c1edfe13..5e12d6963 100644 --- a/Server/src/main/core/net/event/MSReadEvent.java +++ b/Server/src/main/core/net/event/MSReadEvent.java @@ -88,8 +88,7 @@ public final class MSReadEvent extends IoReadEvent { } return buffer.getShort() & 0xFFFF; } - log(this.getClass(), Log.ERR, "Invalid packet [opcode=" + opcode + ", last=" + last + ", queued=" + usedQueuedBuffer + "]!"); return -1; } -} \ No newline at end of file +} diff --git a/Server/src/main/core/net/packet/PacketProcessor.kt b/Server/src/main/core/net/packet/PacketProcessor.kt index e1d151e67..c4b877e1f 100644 --- a/Server/src/main/core/net/packet/PacketProcessor.kt +++ b/Server/src/main/core/net/packet/PacketProcessor.kt @@ -158,6 +158,10 @@ object PacketProcessor { } } is Packet.ReportAbuse -> { + if (pkt.target.isNullOrEmpty()) { + sendMessage(pkt.player, "Invalid player name.") + return + } if (!GameWorld.accountStorage.checkUsernameTaken(pkt.target.lowercase())) { sendMessage(pkt.player, "Invalid player name.") return @@ -571,9 +575,9 @@ object PacketProcessor { player = pkt.player } else if (pkt is Packet.UseWithGroundItem) { val container = getLikelyContainerForIface(pkt.player, pkt.iface) ?: return - item = container[pkt.slot] + item = container[pkt.slot] ?: return itemId = pkt.usedId - node = GroundItemManager.get(pkt.withId, Location.create(pkt.x, pkt.y, pkt.player.location.z), pkt.player) + node = GroundItemManager.get(pkt.withId, Location.create(pkt.x, pkt.y, pkt.player.location.z), pkt.player) ?: return nodeId = pkt.withId type = IntType.GROUNDITEM player = pkt.player diff --git a/Server/src/main/core/net/packet/in/Decoders530.kt b/Server/src/main/core/net/packet/in/Decoders530.kt index b6ae35734..70fcc6e89 100644 --- a/Server/src/main/core/net/packet/in/Decoders530.kt +++ b/Server/src/main/core/net/packet/in/Decoders530.kt @@ -9,6 +9,7 @@ import core.tools.StringUtils import core.tools.SystemLogger import java.io.PrintWriter import java.io.StringWriter +import java.nio.BufferUnderflowException enum class Decoders530(val opcode: Int) { /****************************************** @@ -635,8 +636,13 @@ enum class Decoders530(val opcode: Int) { COMMAND(44) { override fun decode(player: Player, buffer: IoBuffer): Packet { if (buffer.toByteBuffer().remaining() > 1) { - val message = buffer.string.lowercase() - return Packet.Command(player, message) + try { + val message = buffer.string.lowercase() + return Packet.Command(player, message) + } catch (e: BufferUnderflowException) { + player.sendMessage("Something went wrong there! Please try again.") + return Packet.NoProcess() + } } return Packet.NoProcess() } @@ -795,4 +801,4 @@ enum class Decoders530(val opcode: Int) { } } } -} \ No newline at end of file +} diff --git a/Server/src/main/core/net/packet/in/Login.kt b/Server/src/main/core/net/packet/in/Login.kt index c635f41c4..02f89076b 100644 --- a/Server/src/main/core/net/packet/in/Login.kt +++ b/Server/src/main/core/net/packet/in/Login.kt @@ -89,6 +89,9 @@ object Login { } return Pair(AuthResponse.Success, info) + } catch (buf: BufferUnderflowException) { + //some issue in either the data they sent us or how we read it, either way out of scope of this class's handling. + return Pair(AuthResponse.UnexpectedError, null) } catch (e: Exception) { log(this::class.java, Log.ERR, "Exception encountered during login packet parsing! See stack trace below.") e.printStackTrace() diff --git a/Server/src/main/core/net/packet/in/RunScript.kt b/Server/src/main/core/net/packet/in/RunScript.kt index 48b88504b..b3f900714 100644 --- a/Server/src/main/core/net/packet/in/RunScript.kt +++ b/Server/src/main/core/net/packet/in/RunScript.kt @@ -1,6 +1,6 @@ package core.net.packet.`in` -import core.api.sendDialogue +import core.api.* import core.game.node.entity.player.Player /** @@ -12,6 +12,8 @@ object RunScript { fun processInput(player: Player, value: Any, script: ((Any) -> Boolean)) { if (value is Int && value <= 0) return + + val type = player.getAttribute("input-type", InputType.NUMERIC) var input = value @@ -26,10 +28,18 @@ object RunScript { input = input.replace("k", "000").replace("m", "000000") } + if (type == InputType.NUMERIC || type == InputType.AMOUNT) + input = input.toString().toIntOrNull() ?: input + try { script(input) } catch (_: NumberFormatException) { sendDialogue(player, "That number's a bit large, don't you think?") + } catch (_: ClassCastException) { + sendDialogue(player, "Something went wrong here. Try again.") + } finally { + removeAttribute(player, "runscript") + removeAttribute(player, "input-type") } } } diff --git a/Server/src/main/core/tools/StringUtils.java b/Server/src/main/core/tools/StringUtils.java index b0a941986..84ac0e3e0 100644 --- a/Server/src/main/core/tools/StringUtils.java +++ b/Server/src/main/core/tools/StringUtils.java @@ -409,6 +409,7 @@ public final class StringUtils { * @return The string. */ public static String longToString(long l) { + try { int i = 0; char ac[] = new char[32]; while (l != 0L) { @@ -417,6 +418,9 @@ public final class StringUtils { ac[11 - i++] = VALID_CHARS[(int) (l1 - l * 37L)]; } return new String(ac, 12 - i, i); + } catch (ArrayIndexOutOfBoundsException e) { + return ""; + } } /**