diff --git a/Server/src/main/java/core/game/content/activity/duel/DuelSession.java b/Server/src/main/java/core/game/content/activity/duel/DuelSession.java index b80ee902d..7906a540f 100644 --- a/Server/src/main/java/core/game/content/activity/duel/DuelSession.java +++ b/Server/src/main/java/core/game/content/activity/duel/DuelSession.java @@ -13,11 +13,11 @@ import core.game.node.entity.player.Player; import core.game.node.entity.player.info.login.PlayerParser; import core.game.node.entity.state.EntityState; import core.game.node.item.Item; -import core.game.system.monitor.PlayerMonitor; import core.plugin.Plugin; import core.tools.RandomFunction; import kotlin.Unit; import rs09.game.content.global.action.EquipHandler; +import rs09.game.node.entity.player.info.PlayerMonitor; import rs09.game.system.config.ItemConfigParser; import java.text.DecimalFormat; @@ -311,7 +311,7 @@ public final class DuelSession extends ComponentPlugin { log = log.substring(0, log.length() - 1); } log += "}"; - player.getMonitor().log(log, PlayerMonitor.DUEL_LOG); + PlayerMonitor.logMisc(player, "Duel", log); } else { player.removeExtension(DuelSession.class); } diff --git a/Server/src/main/java/core/game/node/entity/player/Player.java b/Server/src/main/java/core/game/node/entity/player/Player.java index e685c844a..5f303f03c 100644 --- a/Server/src/main/java/core/game/node/entity/player/Player.java +++ b/Server/src/main/java/core/game/node/entity/player/Player.java @@ -37,7 +37,6 @@ import core.game.node.entity.skill.summoning.familiar.FamiliarManager; import core.game.node.item.GroundItemManager; import core.game.node.item.Item; import core.game.system.communication.CommunicationInfo; -import core.game.system.monitor.PlayerMonitor; import core.game.system.task.Pulse; import core.game.world.map.*; import core.game.world.map.build.DynamicRegion; @@ -73,6 +72,7 @@ import rs09.game.node.entity.combat.equipment.EquipmentDegrader; import rs09.game.node.entity.player.graves.Grave; import rs09.game.node.entity.player.graves.GraveType; import rs09.game.node.entity.player.graves.GraveController; +import rs09.game.node.entity.player.info.PlayerMonitor; import rs09.game.node.entity.player.info.login.PlayerSaver; import rs09.game.node.entity.skill.runecrafting.PouchManager; import rs09.game.node.entity.state.newsys.State; @@ -88,6 +88,7 @@ import rs09.tools.TickUtilsKt; import rs09.worker.ManagementEvents; import java.util.*; +import java.util.concurrent.TimeUnit; import static api.ContentAPIKt.*; import static rs09.game.node.entity.player.info.stats.StatAttributeKeysKt.STATS_BASE; @@ -255,11 +256,6 @@ public class Player extends Entity { * The farming manager. */ - /** - * The monitor which monitors player actions. - */ - private final PlayerMonitor monitor = new PlayerMonitor(); - /** * Represents the players warning messages. */ @@ -383,6 +379,7 @@ public class Player extends Entity { details.save(); sendLogoutEvents(); Repository.getDisconnectionQueue().add(this); + checkForWealthUpdate(true); } private void sendLogoutEvents() { @@ -475,6 +472,39 @@ public class Player extends Entity { //Decrements prayer points getPrayer().tick(); + + //update wealth tracking + checkForWealthUpdate(false); + } + + private void checkForWealthUpdate(boolean force) { + if (isArtificial()) return; + long previousWealth = getAttribute("last-wealth", -1L); + long lastWealthCheck = getAttribute("last-wealth-check", -1L); + + long nowTime = System.currentTimeMillis(); + if (force || nowTime - lastWealthCheck >= TimeUnit.MINUTES.toMillis(5)) { + long totalWealth = 0L; + for (Item i : inventory.toArray()) { + if (i == null) continue; + totalWealth += (long) i.getDefinition().getValue() * i.getAmount(); + } + for (Item i : bank.toArray()) { + if (i == null) break; + totalWealth += (long) i.getDefinition().getValue() * i.getAmount(); + } + for (Item i : bankSecondary.toArray()) { + if (i == null) break; + totalWealth += (long) i.getDefinition().getValue() * i.getAmount(); + } + + long diff = previousWealth == -1 ? 0L : totalWealth - previousWealth; + setAttribute("/save:last-wealth", totalWealth); + setAttribute("/save:last-wealth-check", nowTime); + + if (diff != 0) + PlayerMonitor.logWealthChange(this, totalWealth, diff); + } } @Override @@ -611,8 +641,11 @@ public class Player extends Entity { Grave g = GraveController.produceGrave(GraveController.getGraveType(this)); g.initialize(this, location, Arrays.stream(c[1].toArray()).filter(Objects::nonNull).toArray(Item[]::new)); //note: the amount of code required to filter nulls from an array in Java is atrocious. } else { + StringBuilder itemsLost = new StringBuilder(); for (Item item : c[1].toArray()) { if (item == null) continue; + if (killer instanceof Player) + itemsLost.append(getItemName(item.getId())).append("(").append(item.getAmount()).append("), "); if (GraveController.shouldCrumble(item.getId())) continue; if (GraveController.shouldRelease(item.getId())) @@ -620,6 +653,8 @@ public class Player extends Entity { item = GraveController.checkTransform(item); GroundItemManager.create(item, location, killer instanceof Player ? (Player) killer : this); } + if (killer instanceof Player) + PlayerMonitor.logMisc((Player) killer, "PK", "Killed " + name + ", who dropped: " + itemsLost); sendMessage(colorize("%RDue to the circumstances of your death, you do not have a grave.")); } @@ -1173,13 +1208,6 @@ public class Player extends Entity { * @return The farmingManager. */ - /** - * Gets the monitor. - * @return The monitor. - */ - public PlayerMonitor getMonitor() { - return monitor; - } /** * Gets the warningMessages. diff --git a/Server/src/main/java/core/game/node/entity/player/link/request/trade/TradeModule.kt b/Server/src/main/java/core/game/node/entity/player/link/request/trade/TradeModule.kt index 193dcde50..b5c115e91 100644 --- a/Server/src/main/java/core/game/node/entity/player/link/request/trade/TradeModule.kt +++ b/Server/src/main/java/core/game/node/entity/player/link/request/trade/TradeModule.kt @@ -7,10 +7,9 @@ import core.game.node.entity.player.Player import core.game.node.entity.player.link.request.RequestModule import core.game.node.item.GroundItemManager import core.game.node.item.Item -import core.game.system.monitor.PlayerMonitor import rs09.game.ai.AIRepository import rs09.game.ai.general.scriptrepository.DoublingMoney -import rs09.game.system.SystemLogger +import rs09.game.node.entity.player.info.PlayerMonitor import java.text.DecimalFormat import java.util.* @@ -352,17 +351,16 @@ class TradeModule * @param module the module. */ private fun giveContainers(module: TradeModule) { - val pContainer: Container? = module.container - val oContainer: Container? = getExtension(module.target)!!.container + val pContainer: Container = module.container ?: return + val oContainer: Container = getExtension(module.target)!!.container ?: return - SystemLogger.logTrade(module.player!!.username + " -> " + module.target!!.username + " " + Arrays.toString(pContainer!!.toArray())) - SystemLogger.logTrade(module.target!!.username + " -> " + module.player!!.username + " " + Arrays.toString(oContainer!!.toArray())) + PlayerMonitor.logTrade(module.player!!, module.target!!, pContainer, oContainer) (AIRepository.PulseRepository[module.player!!.username.lowercase()]?.botScript as DoublingMoney?)?.itemsReceived(module.target!!, oContainer) (AIRepository.PulseRepository[module.target!!.username.lowercase()]?.botScript as DoublingMoney?)?.itemsReceived(module.player!!, pContainer) - addContainer(module.player, module.target, oContainer) - addContainer(module.target, module.player, pContainer) + addContainer(module.player, oContainer) + addContainer(module.target, pContainer) module.target!!.packetDispatch.sendMessage("Accepted trade.") module.player!!.packetDispatch.sendMessage("Accepted trade.") } @@ -373,10 +371,9 @@ class TradeModule * @param target the target who gains the items. * @param container the container. */ - private fun addContainer(player: Player?, target: Player?, container: Container?) { + private fun addContainer(player: Player?, container: Container?) { val c = Container(container!!.itemCount(), ContainerType.ALWAYS_STACK) c.addAll(container) - var log = "traded => " + target!!.name + " received => {" for (i in container.toArray()) { if (i == null) { continue @@ -388,17 +385,6 @@ class TradeModule GroundItemManager.create(i, player) } } - for (i in c.toArray()) { - if (i == null) { - continue - } - log += i.name + " x " + i.amount + "," - } - if (log[log.length - 1] == ',') { - log = log.substring(0, log.length - 1) - } - log += "}" - player!!.monitor.log(log, PlayerMonitor.TRADE_LOG) } /** diff --git a/Server/src/main/java/core/game/node/entity/skill/Skills.java b/Server/src/main/java/core/game/node/entity/skill/Skills.java index 2bcb34c5c..028e4ba2f 100644 --- a/Server/src/main/java/core/game/node/entity/skill/Skills.java +++ b/Server/src/main/java/core/game/node/entity/skill/Skills.java @@ -14,8 +14,10 @@ import core.game.world.update.flag.player.AppearanceFlag; import core.net.packet.PacketRepository; import core.net.packet.context.SkillContext; import core.net.packet.out.SkillLevel; +import kotlin.Pair; import org.json.simple.JSONArray; import org.json.simple.JSONObject; +import rs09.game.node.entity.player.info.PlayerMonitor; import rs09.game.node.entity.skill.skillcapeperks.SkillcapePerks; import rs09.game.world.GameWorld; import rs09.game.world.repository.Repository; @@ -23,6 +25,8 @@ import rs09.plugin.CorePluginTypes.XPGainPlugins; import org.rs09.consts.Items; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; import static java.lang.Math.floor; import static java.lang.Math.max; @@ -68,6 +72,10 @@ public final class Skills { */ private final double[] experience; + private double[] lastUpdateXp = null; + + private int lastUpdate = GameWorld.getTicks(); + /** * An array containing all the maximum levels. */ @@ -212,6 +220,8 @@ public final class Skills { * @param experience The experience. */ public void addExperience(int slot, double experience, boolean playerMod) { + if (lastUpdateXp == null) + lastUpdateXp = this.experience.clone(); double mod = getExperienceMod(slot, experience, playerMod, true); final Player player = entity instanceof Player ? ((Player) entity) : null; final AssistSession assist = entity.getExtension(AssistSession.class); @@ -278,6 +288,18 @@ public final class Skills { PacketRepository.send(SkillLevel.class, new SkillContext((Player) entity, slot)); entity.dispatch(new XPGainEvent(slot, experienceAdd)); } + if (GameWorld.getTicks() - lastUpdate >= 200) { + ArrayList> diffs = new ArrayList<>(); + for (int i = 0; i < this.experience.length; i++) { + double diff = this.experience[i] - lastUpdateXp[i]; + if (diff != 0.0) { + diffs.add(new Pair<>(i, diff)); + } + } + PlayerMonitor.logXpGains(player, diffs); + lastUpdateXp = this.experience.clone(); + lastUpdate = GameWorld.getTicks(); + } } /** diff --git a/Server/src/main/java/core/game/system/SystemTermination.java b/Server/src/main/java/core/game/system/SystemTermination.java index fee8b713c..587e1e157 100644 --- a/Server/src/main/java/core/game/system/SystemTermination.java +++ b/Server/src/main/java/core/game/system/SystemTermination.java @@ -7,6 +7,7 @@ import rs09.Server; import rs09.ServerConstants; import rs09.ServerStore; import rs09.game.ai.AIRepository; +import rs09.game.node.entity.player.info.PlayerMonitor; import rs09.game.system.SystemLogger; import rs09.game.world.GameWorld; import rs09.game.world.repository.Repository; @@ -55,6 +56,7 @@ public final class SystemTermination { } } GameWorld.getShutdownListeners().forEach(ShutdownListener::shutdown); + PlayerMonitor.flushRemainingEventsImmediately(); ServerStore s = null; for (PersistWorld wld : GameWorld.getWorldPersists()) { if (wld instanceof ServerStore) diff --git a/Server/src/main/java/core/game/system/communication/ClanRepository.java b/Server/src/main/java/core/game/system/communication/ClanRepository.java index b5697530e..0c77f1043 100644 --- a/Server/src/main/java/core/game/system/communication/ClanRepository.java +++ b/Server/src/main/java/core/game/system/communication/ClanRepository.java @@ -5,9 +5,9 @@ import core.game.content.activity.ActivityPlugin; import core.game.node.entity.player.Player; import core.game.node.entity.player.info.PlayerDetails; import core.game.node.entity.player.info.Rights; -import core.game.system.monitor.PlayerMonitor; import proto.management.ClanJoinNotification; import proto.management.ClanLeaveNotification; +import rs09.game.node.entity.player.info.PlayerMonitor; import rs09.game.world.GameWorld; import rs09.game.world.repository.Repository; import core.net.amsc.WorldCommunicator; @@ -198,9 +198,7 @@ public final class ClanRepository { return; } } - StringBuilder sb = new StringBuilder(message); - sb.append(" => ").append(name).append(" (owned by ").append(owner).append(")"); - player.getMonitor().log(sb.toString(), PlayerMonitor.CLAN_CHAT_LOG); + PlayerMonitor.logChat(player, "clan", message); for (Iterator it = players.iterator(); it.hasNext();) { ClanEntry entry = it.next(); Player p = entry.getPlayer(); diff --git a/Server/src/main/java/core/game/system/communication/CommunicationInfo.java b/Server/src/main/java/core/game/system/communication/CommunicationInfo.java index 59097694a..95bf5239e 100644 --- a/Server/src/main/java/core/game/system/communication/CommunicationInfo.java +++ b/Server/src/main/java/core/game/system/communication/CommunicationInfo.java @@ -6,7 +6,6 @@ import org.jetbrains.annotations.NotNull; import proto.management.PrivateMessage; import rs09.auth.UserAccountInfo; import rs09.game.system.SystemLogger; -import core.game.system.monitor.PlayerMonitor; import core.game.system.mysql.SQLTable; import core.game.system.task.Pulse; import rs09.game.world.GameWorld; diff --git a/Server/src/main/java/core/game/system/monitor/PlayerMonitor.java b/Server/src/main/java/core/game/system/monitor/PlayerMonitor.java deleted file mode 100644 index 66ce2027f..000000000 --- a/Server/src/main/java/core/game/system/monitor/PlayerMonitor.java +++ /dev/null @@ -1,284 +0,0 @@ -package core.game.system.monitor; - -import core.cache.misc.buffer.ByteBufferUtils; -import core.game.node.entity.player.Player; - - -import java.nio.ByteBuffer; - -/** - * Handles the player monitoring. - * @author Emperor - */ -public final class PlayerMonitor { - - /** - * The public chat log. - */ - public static final int PUBLIC_CHAT_LOG = 0; - - /** - * The private chat log. - */ - public static final int PRIVATE_CHAT_LOG = 1; - - /** - * The clan chat log. - */ - public static final int CLAN_CHAT_LOG = 2; - - /** - * The IP/MAC-address log. - */ - public static final int ADDRESS_LOG = 3; - - /** - * The command log. - */ - public static final int COMMAND_LOG = 4; - - /** - * The trade log. - */ - public static final int TRADE_LOG = 5; - - /** - * The GE logs. - */ - public static final int GRAND_EXCHANGE_LOG = 6; - - /** - * The duel stake logs. - */ - public static final int DUEL_LOG = 7; - - /** - * The macro flag for a click when the client was out of focus. - */ - public static final int MF_NO_FOCUS_CLICK = 0x1; - - /** - * The networth of the player. - */ - private long networth; - - /** - * If the client is currently focused. - */ - private boolean clientFocus = true; - - /** - * How likely it is this player has used a macro. - */ - private int macroFlag; - - /** - * The duplication log. - */ - private DuplicationLog duplicationLog; - - /** - * The message monitors. - */ - private MessageLog[] logs = new MessageLog[8]; - - /** - * Constructs a new {@code PlayerMonitor} {@code Object}. - */ - public PlayerMonitor() { - logs[PUBLIC_CHAT_LOG] = new MessageLog(500); - logs[PRIVATE_CHAT_LOG] = new MessageLog(500); - logs[CLAN_CHAT_LOG] = new MessageLog(500); - logs[ADDRESS_LOG] = new MessageLog(200); - logs[COMMAND_LOG] = new MessageLog(200); - logs[TRADE_LOG] = new MessageLog(200); - logs[GRAND_EXCHANGE_LOG] = new MessageLog(500); - logs[DUEL_LOG] = new MessageLog(200); - } - - /** - * Clears the logs. - */ - public void clear() { - for (int i = 0; i < logs.length; i++) { - if (logs[i] == null) { - continue; - } - logs[i].getMessages().clear(); - } - duplicationLog.getMessages().clear(); - } - - /** - * If the player's actions should be logged. - * @return {@code True} if so. - */ - public boolean isLogActions() { - return duplicationLog != null && duplicationLog.isLoggingFlagged(); - } - - /** - * Logs the given string. - * @param string The string to log. - * @param type The log type. - */ - public void log(String string, int type) { - log(string, type, true); - } - - /** - * Logs the given string. - * @param string The string to log. - * @param type The log type. - * @param timeStamp If we should time stamp the logged string. - */ - public void log(String string, int type, boolean timeStamp) { - if (type < 3) { - String check = string.toLowerCase(); - if (check.contains("dupe") || check.contains("duping") || check.contains("bug") || check.contains("glitch") || check.contains("exploit")) { - getDuplicationLog().flag(DuplicationLog.DUPE_TALK); - String prefix = null; - if (type == PUBLIC_CHAT_LOG) { - prefix = "Public"; - } else if (type == PRIVATE_CHAT_LOG) { - prefix = "Private"; - } else { - prefix = "Clan"; - } - getDuplicationLog().log(prefix + " chat message: " + string, true); - } - } - logs[type].log(string, timeStamp); - } - - /** - * Checks the networth difference for a player (called on logout). - * @param player The player. - * @param currentNet The current networth. - */ - public void checkNetworth(Player player, long currentNet) { - long difference = currentNet - this.networth; - if (difference < 1) { // The player lost money. - return; - } - if (difference > 150_000_000l) { - getDuplicationLog().flag(DuplicationLog.NW_INCREASE); - getDuplicationLog().log("Large networth increase - [incr=" + difference + ", old=" + this.networth + ", cur=" + currentNet + "].", true); - } - } - - /** - * Called when the player does a mouse click. - * @param x The x-coordinate of the cursor. - * @param y The y-coordinate of the cursor. - * @param delay The time between this click and last. - * @param rightClick If the right mouse-button was used to click. - */ - public void handleMouseClick(int x, int y, int delay, boolean rightClick) { - if (!clientFocus) { - macroFlag |= MF_NO_FOCUS_CLICK; - } - } - - /** - * Gets the address log. - * @param original the original log. - * @param address the address. - * @return the address. - */ - public static String getAddressLog(String original, String address) { - String log = ""; - if (original != null && original.length() > 0) { - log += original; - if (log.charAt(log.length() - 1) != '|') { - log += "|"; - } - } - if (address != null && address.length() > 0 && (original == null || !original.contains(address))) { - log += address + "|"; - } - if (log.length() > 0 && log.charAt(log.length() - 1) == '|') { - log = log.substring(0, log.length() - 1); - } - if (log == null) { - log = ""; - } - return log; - } - - /** - * Adds a macro flag. - * @param flag The flag. - */ - public void addMacroFlag(int flag) { - macroFlag |= flag; - } - - /** - * Gets the macroFlag. - * @return The macroFlag. - */ - public int getMacroFlag() { - return macroFlag; - } - - /** - * Sets the macroFlag. - * @param macroFlag The macroFlag to set. - */ - public void setMacroFlag(int macroFlag) { - this.macroFlag = macroFlag; - } - - /** - * Gets the networth. - * @return The networth. - */ - public long getNetworth() { - return networth; - } - - /** - * Sets the networth. - * @param networth The networth to set. - */ - public void setNetworth(long networth) { - this.networth = networth; - } - - /** - * Gets the clientFocus. - * @return The clientFocus. - */ - public boolean isClientFocus() { - return clientFocus; - } - - /** - * Sets the clientFocus. - * @param clientFocus The clientFocus to set. - */ - public void setClientFocus(boolean clientFocus) { - this.clientFocus = clientFocus; - } - - /** - * Gets the logs. - * @return The logs. - */ - public MessageLog[] getLogs() { - return logs; - } - - /** - * Gets the duplicationLog. - * @return The duplicationLog. - */ - public DuplicationLog getDuplicationLog() { - if (duplicationLog == null) { - duplicationLog = new DuplicationLog(); - } - return duplicationLog; - } - -} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/content/global/action/PickupHandler.kt b/Server/src/main/kotlin/rs09/game/content/global/action/PickupHandler.kt index e92f64f35..32d2f5118 100644 --- a/Server/src/main/kotlin/rs09/game/content/global/action/PickupHandler.kt +++ b/Server/src/main/kotlin/rs09/game/content/global/action/PickupHandler.kt @@ -1,6 +1,7 @@ package rs09.game.content.global.action import api.events.PickUpEvent +import api.getItemName import core.game.content.dialogue.FacialExpression import core.game.content.global.GodType import core.game.node.entity.player.Player @@ -13,6 +14,7 @@ import core.game.node.item.Item import core.game.world.map.RegionManager import core.game.world.update.flag.context.Animation import org.rs09.consts.Items +import rs09.game.node.entity.player.info.PlayerMonitor import rs09.game.system.SystemLogger import rs09.game.system.config.GroundSpawnLoader import rs09.game.world.GameWorld @@ -55,6 +57,9 @@ object PickupHandler { return true } if (item.isActive && player.inventory.add(add)) { + if (item.dropper is Player && item.dropper.details.uid != player.details.uid){ + PlayerMonitor.logMisc(item.dropper, "DropTrade", "${getItemName(item.id)} x${item.amount} picked up by ${player.name}.") + } if (!RegionManager.isTeleportPermitted(item.location)) { player.animate(Animation.create(535)) } diff --git a/Server/src/main/kotlin/rs09/game/node/entity/player/info/PlayerMonitor.kt b/Server/src/main/kotlin/rs09/game/node/entity/player/info/PlayerMonitor.kt new file mode 100644 index 000000000..63ecac0c8 --- /dev/null +++ b/Server/src/main/kotlin/rs09/game/node/entity/player/info/PlayerMonitor.kt @@ -0,0 +1,256 @@ +package rs09.game.node.entity.player.info + +import api.getItemName +import core.game.container.Container +import core.game.node.entity.player.Player +import core.game.node.entity.skill.Skills +import discord.Discord +import kotlinx.coroutines.* +import org.sqlite.SQLiteDataSource +import rs09.ServerConstants +import rs09.game.system.SystemLogger +import java.io.File +import java.sql.Connection +import java.util.concurrent.LinkedBlockingQueue +import kotlin.math.abs + +object PlayerMonitor { + private val eventQueue = LinkedBlockingQueue() + private var activeTask: Job? = null + + @JvmStatic fun logWealthChange(player: Player, totalWealth: Long, diff: Long) { + val event = LogEvent.WealthLog( + player.name, + player.details.uid, + totalWealth, + diff, + System.currentTimeMillis() + ) + dispatch(event) + if (abs(diff) >= 500_000L) + Discord.postPlayerAlert(player.name, "Dubious change in wealth: $diff, total wealth: $totalWealth") + } + + @JvmStatic fun logChat(player: Player, type: String, message: String) { + val event = LogEvent.ChatLog( + player.name, + player.details.uid, + type, + message, + System.currentTimeMillis() + ) + checkForFlaggedText(player.name, message) + dispatch(event) + } + + @JvmStatic fun logTrade(player1: Player, player2: Player, container1: Container, container2: Container) { + val container1String = StringBuilder() + val container2String = StringBuilder() + + for (item in container1.toArray()) { + item ?: continue + container1String.append(getItemName(item.id) + "(${item.amount}), ") + } + + for (item in container2.toArray()) { + item ?: continue + container2String.append(getItemName(item.id) + "(${item.amount}), ") + } + + val event = LogEvent.TradeLog( + player1.name, + player1.details.uid, + player2.name, + player2.details.uid, + container1String.toString(), + container2String.toString(), + System.currentTimeMillis() + ) + dispatch(event) + } + + @JvmStatic fun logPrivateChat(sender: Player, receiver: String, message: String) { + val event = LogEvent.ChatLog( + sender.name, + sender.details.uid, + "private", + "=> $receiver: $message", + System.currentTimeMillis() + ) + checkForFlaggedText(sender.name, message) + dispatch(event) + } + + @JvmStatic fun logMisc(player: Player, type: String, details: String) { + val event = LogEvent.MiscLog( + player.name, + player.details.uid, + type, + details, + System.currentTimeMillis() + ) + dispatch(event) + } + + @JvmStatic fun logXpGains(player: Player, xpDiff: List>) { + if (player.isArtificial) return + if (xpDiff.isEmpty()) return + val query = StringBuilder("INSERT INTO xp_gains(player,uid,") + val xpNames = StringBuilder() + val xpAmounts = StringBuilder() + for ((skillId, amount) in xpDiff) { + xpNames.append(Skills.SKILL_NAME[skillId].lowercase() + ",") + xpAmounts.append("$amount,") + } + query.append(xpNames) + query.append("timestamp) VALUES(") + query.append("'" + player.name + "',") + query.append(player.details.uid.toString() + ",") + query.append(xpAmounts) + query.append(System.currentTimeMillis().toString()) + query.append(");") + dispatch(LogEvent.XpLog(query.toString())) + } + + private fun dispatch(event: LogEvent) { + eventQueue.put(event) + if (eventQueue.size >= 50) { + processQueuedEvents() + } + } + + fun processQueuedEvents() { + val path = ServerConstants.LOGS_PATH + "playerlogs.db" + if (activeTask?.isActive == true) + return + if (!File(path).exists()) { + createSqliteDatabase(path) + } + activeTask = GlobalScope.launch { + val conn = connect(path) + conn.use { + while (eventQueue.isNotEmpty()) { + val event = withContext(Dispatchers.IO) { eventQueue.take() } + process(event, it) + } + } + } + } + + @JvmStatic fun flushRemainingEventsImmediately() { + SystemLogger.logInfo(this::class.java, "Flushing player log events...") + val path = ServerConstants.LOGS_PATH + "playerlogs.db" + if (!File(path).exists()) { + createSqliteDatabase(path) + } + if (activeTask != null) + activeTask?.cancel("Interrupted by shutdown. This is probably fine.") + val conn = connect(path) + conn.use { + while (eventQueue.isNotEmpty()) + process(eventQueue.take(), it) + } + } + + private fun connect(path: String) : Connection { + val source = SQLiteDataSource() + source.url = "jdbc:sqlite:$path" + return source.connection + } + + private fun createSqliteDatabase(path: String) { + if (!File(ServerConstants.LOGS_PATH).exists()) + File(ServerConstants.LOGS_PATH).mkdirs() + val connection = connect(path) + val stmt = connection.createStatement() + stmt.execute( + """ + CREATE TABLE "chat_logs" ( "player" TEXT, "uid" INTEGER, "type" TEXT, "message" TEXT, "timestamp" NUMERIC ); + """) + stmt.execute( + """ + CREATE TABLE "misc_logs" ( "player" TEXT, "uid" INTEGER, "type" TEXT, "details" TEXT , "timestamp" NUMERIC); + """) + stmt.execute( + """ + CREATE TABLE "trade_logs" ( "player_a" TEXT, "player_b" TEXT, "uid_a" INTEGER, "uid_b" INTEGER, "items_a" TEXT, "items_b" TEXT, "timestamp" NUMERIC ); + """) + stmt.execute( + """ + CREATE TABLE "xp_gains" ( "player" TEXT, "uid" INTEGER, "attack" INTEGER, "defence" INTEGER, "strength" INTEGER, "hitpoints" INTEGER, "ranged" INTEGER, "prayer" INTEGER, "magic" INTEGER, "cooking" INTEGER, "woodcutting" INTEGER, "fletching" INTEGER, "fishing" INTEGER, "firemaking" INTEGER, "crafting" INTEGER, "smithing" INTEGER, "mining" INTEGER, "herblore" INTEGER, "agility" INTEGER, "thieving" INTEGER, "slayer" INTEGER, "farming" INTEGER, "runecrafting" INTEGER, "hunter" INTEGER, "construction" INTEGER, "summoning" INTEGER , "timestamp" NUMERIC) + """) + stmt.execute( + """ + CREATE TABLE "wealth_logs" ( "player" TEXT, "uid" INTEGER, "total" NUMERIC, "diff" NUMERIC, "timestamp" NUMERIC ) + """) + stmt.close() + connection.close() + } + + private fun process(event: LogEvent, conn: Connection) { + when (event) { + is LogEvent.ChatLog -> { + val stmt = conn.prepareStatement(CHAT_LOG_INSERT) + stmt.setString(1, event.player) + stmt.setInt(2, event.uid) + stmt.setString(3, event.type) + stmt.setString(4, event.message) + stmt.setLong(5, event.timestamp) + stmt.execute() + } + is LogEvent.TradeLog -> { + val stmt = conn.prepareStatement(TRADE_LOG_INSERT) + stmt.setString(1, event.player1) + stmt.setString(2, event.player2) + stmt.setInt(3, event.uid1) + stmt.setInt(4, event.uid2) + stmt.setString(5, event.items1) + stmt.setString(6, event.items2) + stmt.setLong(7, event.timestamp) + stmt.execute() + } + is LogEvent.MiscLog -> { + val stmt = conn.prepareStatement(MISC_LOG_INSERT) + stmt.setString(1, event.player) + stmt.setInt(2, event.uid) + stmt.setString(3, event.type) + stmt.setString(4, event.details) + stmt.setLong(5, event.timestamp) + stmt.execute() + } + is LogEvent.XpLog -> { + val stmt = conn.createStatement() + stmt.execute(event.query) + } + is LogEvent.WealthLog -> { + val stmt = conn.prepareStatement(WEALTH_LOG_INSERT) + stmt.setString(1, event.player) + stmt.setInt(2, event.uid) + stmt.setLong(3, event.total) + stmt.setLong(4, event.diff) + stmt.setLong(5, event.timeStamp) + stmt.execute() + } + } + } + + fun checkForFlaggedText(username: String, message: String) { + if (message.contains("dupe") || message.contains("bug") || message.contains("exploit") || message.contains("glitch")) { + Discord.postPlayerAlert(username, "Flagged Chat - $message") + } + } + + private sealed class LogEvent { + data class ChatLog(val player: String, val uid: Int, val type: String, val message: String, val timestamp: Long) : LogEvent() + data class TradeLog(val player1: String, val uid1: Int, val player2 : String, val uid2: Int, val items1: String, val items2: String, val timestamp: Long) : LogEvent() + data class MiscLog(val player: String, val uid: Int, val type: String, val details: String, val timestamp: Long) : LogEvent() + data class XpLog(val query: String) : LogEvent() + data class WealthLog(val player: String, val uid: Int, val total: Long, val diff: Long, val timeStamp: Long) : LogEvent() + } + + private const val CHAT_LOG_INSERT = "INSERT INTO chat_logs(player,uid,type,message,timestamp) VALUES (?,?,?,?,?);" + private const val TRADE_LOG_INSERT = "INSERT INTO trade_logs(player_a,player_b,uid_a,uid_b,items_a,items_b,timestamp) VALUES (?,?,?,?,?,?,?);" + private const val MISC_LOG_INSERT = "INSERT INTO misc_logs(player,uid,type,details,timestamp) VALUES (?,?,?,?,?);" + private const val WEALTH_LOG_INSERT = "INSERT INTO wealth_logs(player,uid,total,diff,timestamp) VALUES (?,?,?,?,?);" + +} \ No newline at end of file diff --git a/Server/src/main/kotlin/rs09/game/node/entity/player/info/login/LoginParser.kt b/Server/src/main/kotlin/rs09/game/node/entity/player/info/login/LoginParser.kt index 8bc079002..1efafd19f 100644 --- a/Server/src/main/kotlin/rs09/game/node/entity/player/info/login/LoginParser.kt +++ b/Server/src/main/kotlin/rs09/game/node/entity/player/info/login/LoginParser.kt @@ -7,7 +7,6 @@ import core.game.node.entity.player.info.login.LoginConfiguration import core.game.node.entity.player.info.login.PlayerParser import core.game.node.entity.player.info.login.Response import core.game.system.SystemManager -import core.game.system.monitor.PlayerMonitor import core.game.system.task.Pulse import rs09.auth.AuthResponse import rs09.game.world.GameWorld @@ -53,9 +52,6 @@ class LoginParser(val details: PlayerDetails) { flag(AuthResponse.Success) player.init() } - player.monitor.log(player.details.ipAddress, PlayerMonitor.ADDRESS_LOG) - player.monitor.log(player.details.serial, PlayerMonitor.ADDRESS_LOG) - player.monitor.log(player.details.macAddress, PlayerMonitor.ADDRESS_LOG) } else { Repository.playerNames.remove(player.name) } diff --git a/Server/src/main/kotlin/rs09/game/node/entity/player/info/login/PlayerSaveParser.kt b/Server/src/main/kotlin/rs09/game/node/entity/player/info/login/PlayerSaveParser.kt index c63710b73..a07e70e1b 100644 --- a/Server/src/main/kotlin/rs09/game/node/entity/player/info/login/PlayerSaveParser.kt +++ b/Server/src/main/kotlin/rs09/game/node/entity/player/info/login/PlayerSaveParser.kt @@ -208,19 +208,6 @@ class PlayerSaveParser(val player: Player) { } fun parseMonitor() { - val monitorData = saveFile!!["playerMonitor"] as JSONObject - if (monitorData.containsKey("duplicationFlag")) { - val duplicationFlag: Int = (monitorData.get("duplicationFlag") as String).toInt() - player.monitor.duplicationLog.flag(duplicationFlag) - } - if (monitorData.containsKey("macroFlag")) { - val macroFlag: Int = (monitorData.get("macroFlag") as String).toInt() - player.monitor.macroFlag = macroFlag - } - if (monitorData.containsKey("lastIncreaseFlag")) { - val lastIncreaseFlag: Long = (monitorData.get("lastIncreaseFlag") as String).toLong() - player.monitor.duplicationLog.lastIncreaseFlag = lastIncreaseFlag - } } fun parseConfigs() { diff --git a/Server/src/main/kotlin/rs09/game/node/entity/player/info/login/PlayerSaver.kt b/Server/src/main/kotlin/rs09/game/node/entity/player/info/login/PlayerSaver.kt index 7a2cbebf1..417b246af 100644 --- a/Server/src/main/kotlin/rs09/game/node/entity/player/info/login/PlayerSaver.kt +++ b/Server/src/main/kotlin/rs09/game/node/entity/player/info/login/PlayerSaver.kt @@ -304,17 +304,7 @@ class PlayerSaver (val player: Player){ } fun savePlayerMonitor(root: JSONObject){ - val playerMonitor = JSONObject() - if(player.monitor.duplicationLog != null && player.monitor.duplicationLog.flag != 0){ - playerMonitor.put("duplicationFlag",player.monitor.duplicationLog.flag.toString()) - } - if(player.monitor.macroFlag != 0){ - playerMonitor.put("macroFlag",player.monitor.macroFlag.toString()) - } - if(player.monitor.duplicationLog != null && player.monitor.duplicationLog.isLoggingFlagged){ - playerMonitor.put("lastIncreaseFlag",player.monitor.duplicationLog.lastIncreaseFlag.toString()) - } - root.put("playerMonitor",playerMonitor) + } fun saveConfigs(root: JSONObject){ diff --git a/Server/src/main/kotlin/rs09/net/packet/PacketProcessor.kt b/Server/src/main/kotlin/rs09/net/packet/PacketProcessor.kt index a394e2e80..48da3cddf 100644 --- a/Server/src/main/kotlin/rs09/net/packet/PacketProcessor.kt +++ b/Server/src/main/kotlin/rs09/net/packet/PacketProcessor.kt @@ -29,7 +29,6 @@ import core.game.node.item.Item import core.game.node.scenery.Scenery import core.game.system.communication.ClanRank import core.game.system.communication.CommunicationInfo -import core.game.system.monitor.PlayerMonitor import core.game.system.task.Pulse import core.game.world.map.Location import core.game.world.map.RegionManager @@ -54,6 +53,7 @@ import rs09.game.interaction.InteractionListeners import rs09.game.interaction.InterfaceListeners import rs09.game.interaction.QCRepository import rs09.game.interaction.inter.ge.StockMarket +import rs09.game.node.entity.player.info.PlayerMonitor import rs09.game.node.entity.skill.magic.SpellListener import rs09.game.node.entity.skill.magic.SpellListeners import rs09.game.node.entity.skill.magic.SpellUtils @@ -199,8 +199,8 @@ object PacketProcessor { pkt.player.interfaceManager.closeChatbox() } is Packet.Command -> { - if (CommandSystem.commandSystem.parse(pkt.player, pkt.commandLine)) - pkt.player.monitor.log(pkt.commandLine, PlayerMonitor.COMMAND_LOG) + PlayerMonitor.logMisc(pkt.player, "CommandUse", pkt.commandLine) + CommandSystem.commandSystem.parse(pkt.player, pkt.commandLine) } is Packet.ChatMessage -> { if (pkt.player.details.isMuted) @@ -215,7 +215,7 @@ object PacketProcessor { ManagementEvents.publish(builder.build()) return } - pkt.player.monitor.log(pkt.message, PlayerMonitor.PUBLIC_CHAT_LOG) + PlayerMonitor.logChat(pkt.player, "public", pkt.message) val ctx = ChatMessage(pkt.player, pkt.message, pkt.effects, pkt.message.length) pkt.player.updateMasks.register(ChatFlag(ctx)) } @@ -229,6 +229,7 @@ object PacketProcessor { pkt.player.sendMessage("You have been muted due to breaking a rule.") else CommunicationInfo.sendMessage(pkt.player, pkt.username, pkt.message) + PlayerMonitor.logPrivateChat(pkt.player, pkt.username, pkt.message) } is Packet.PacketCountUpdate -> { val final = pkt.count - pkt.player.interfaceManager.getPacketCount(0) @@ -282,7 +283,7 @@ object PacketProcessor { private fun processTrackingPacket(pkt: Packet) { when(pkt) { - is Packet.TrackingFocus -> pkt.player.monitor.isClientFocus = pkt.isFocused + is Packet.TrackingFocus -> {} is Packet.TrackingDisplayUpdate -> { pkt.player.session.clientInfo.screenWidth = pkt.screenWidth pkt.player.session.clientInfo.screenHeight = pkt.screenHeight @@ -298,9 +299,7 @@ object PacketProcessor { } is Packet.TrackingMouseClick -> { //TODO see above todo - if (!pkt.player.monitor.isClientFocus) { - Discord.postPlayerAlert(pkt.player.username, "Mouseclick without window focus!") - } + } } } diff --git a/Server/src/main/kotlin/rs09/net/packet/in/Login.kt b/Server/src/main/kotlin/rs09/net/packet/in/Login.kt index 5fd3f1a00..080d3e07a 100644 --- a/Server/src/main/kotlin/rs09/net/packet/in/Login.kt +++ b/Server/src/main/kotlin/rs09/net/packet/in/Login.kt @@ -19,6 +19,7 @@ import rs09.ServerStore import rs09.ServerStore.Companion.addToList import rs09.ServerStore.Companion.getList import rs09.auth.AuthResponse +import rs09.game.node.entity.player.info.PlayerMonitor import rs09.game.node.entity.player.info.login.LoginParser import rs09.game.system.SystemLogger import rs09.game.world.GameWorld @@ -131,6 +132,7 @@ object Login { } val player = Player(details) + PlayerMonitor.logMisc(player, "login_ip", details.ipAddress) if (canBypassAccountLimitCheck(player)) { proceedWithAcceptableLogin(session, player, opcode) } else {