Exception fixes

Fixed a bug that could occasionally cause the sandwich lady interface to become unresponsive
Fixed an issue that could cause shade random events to not spawn for max-level players
Fixed an issue that could cause zombie random events to not spawn for max-level players
Fixed a widespread book issue that resulted from not resetting the current page index when closing a book, and then trying to open another book with less pages.
Converted bogrog pouch swap to the generic item select interface, and removed relevant code from the grand exchange code
Fixed an issue that could occasionally prevent exchange of item sets
Fixed an issue with Diango's reclaim interface that could result in items other than the ones you clicked being claimed
Made incubation moderately more reliable, and incubation timers now pass even while offline
Fixed an issue that prevented the leather crafting dialogue from accepting tokenized amounts (e.g. 100k)
Fixed an issue that prevented the tool leprechaun interface from allowing tokenized amounts
Fixed an issue that prevented players from using tokenized amounts in the ogleroot purchase dialogue for vinesweeper
Fixed an issue that prevented Zaff's batttlestaff dialogue from accepting tokenized amounts
Fixed an issue that prevented shop buy-x options from accepting tokenized amounts
Various shop-related exception fixes
Added some ingame player messages for when a Hunter "catch" option isn't being handled properly
Fixed some exceptions being thrown by lunar spells
Pyramid Plunder is now better at detecting when a player is in the minigame erroneously and kicking them out
Fixed an issue that would lead to incorrect Kalphite Queen behavior when killing her with multiple players in the room
Fixed an issue that could cause the Bedabin Nomad's dialogue to hang during Tourist Trap
Fixed several more issues with Fight Arena cutscenes
Fixed an issue that could prevent the dwarf dialogue from opening while trying to enter the mountain shortcut before finishing Fishing Contest
Fixed an issue that could cause dialogue option hangs in certain unusual circumstances
Fixed an issue that could result in non-movement for interactions where the override location was the same as the target node's location and there was no destination flag set
Fixed an issue where setting invalid node data could cause issues with NPC rendering
Server can now detect when a session is sending up multiple invalid packets and automatically disconnect the player
Fixed an issue that could lead to unintended saving of varps/varbits
Fixed an issue that could cause some interactions in shops to silently fail. The user will now receive a relevant message when these situations occur
Users may now find they could occasionally receive a very snarky response when incorrectly using commands
Users now receive messages when they try to make use of unimplemented friends/clan/etc features.
Errors in string parsing will no longer result in "dead" inputs when attempting to add friends/update clan ranks/etc
Fixed an issue that could lead to loss of some alerts in the Discord integration. Discord integration will now detect problems and queue the failed messages to be resent later.
Added more safety checks to item-on-grounditem packet handling to prevent throwing exceptions
Exceptions in string parsing for command packets will now inform the user that something went wrong, and ask them to try again.
Improved drop table parsing and fixed incorrect drop tables
This commit is contained in:
Ceikry
2023-07-17 08:12:57 +00:00
committed by Ryan
parent 0372937353
commit 845d8b383b
61 changed files with 547 additions and 337 deletions
+18 -6
View File
@@ -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
*/
@@ -0,0 +1,3 @@
package core.api.utils
class ConfigParseException (message: String) : Exception (message)
@@ -27,10 +27,26 @@ open class WeightBasedTable : ArrayList<WeightedItem>() {
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<Item> {
val items = ArrayList<Item>()
for (i in 0 until rollCount)
items.addAll(roll(receiver))
return items
}
open fun roll(receiver: Entity? = null): ArrayList<Item>{
val items = ArrayList<WeightedItem>()
items.addAll(guaranteedItems)
@@ -40,7 +56,7 @@ open class WeightBasedTable : ArrayList<WeightedItem>() {
}
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<WeightedItem>() {
}
return builder.toString()
}
}
}
@@ -20,6 +20,7 @@ abstract class DialogueFile {
this.player = player
this.npc = npc
this.interpreter = interpreter
interpreter.activeTopics.clear()
return this
}
@@ -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<DialogueAction> getActions() {
return actions;
}
}
}
@@ -194,6 +194,7 @@ public abstract class DialoguePlugin implements Plugin<Player> {
* @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];
}
@@ -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;
}
}
@@ -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());
}
/**
@@ -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) {
+2
View File
@@ -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;
}
@@ -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.
@@ -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.");
}
}
}
@@ -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()
@@ -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) {
@@ -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
}
}
}
}
@@ -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) {
+13
View File
@@ -158,6 +158,12 @@ class Shop(val title: String, val stock: Array<ShopItem>, val general: Boolean =
val isMainStock = getAttribute(player, "shop-main", true)
val cont = if (isMainStock) getAttribute<Container?>(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<ShopItem>, val general: Boolean =
{
val shopCont = getAttribute<Container?>(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
}
+8 -2
View File
@@ -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<Shop?>(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)
}
@@ -258,6 +258,10 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){
reject(player, "Usage: ::commands <lt>page<gt>")
}
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)
}
@@ -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){
}
}
}
}
}
@@ -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) {
@@ -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)
}
}
}
}
@@ -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<Pair<String, String>>()
private var flushingPends = false
private val removeList = ArrayList<Pair<String, String>>()
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<EmbedField>): 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<EmbedField>): 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)
}
}
}
}
}
@@ -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.");
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -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
@@ -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) {
}
}
}
}
}
@@ -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()
@@ -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")
}
}
}
@@ -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 "";
}
}
/**