mirror of
https://gitlab.com/2009scape/2009scape.git
synced 2025-12-10 10:20:41 -07:00
Fixed a bug that was causing players to get stuck logged in
Fixed a bug that would cause players to get stuck in a client crash loop when logging out inside of a POH Fixed a bug that let players reach objects that shouldn't be reachable General disconnection reliability improvements Adjusted the color of global chat for HD mode, the new color is #f1b04c
This commit is contained in:
parent
c2cb359bf7
commit
ff18f69dd0
14 changed files with 91 additions and 151 deletions
|
|
@ -18,6 +18,7 @@ import core.game.world.map.zone.ZoneBorders;
|
|||
import core.game.world.map.zone.ZoneBuilder;
|
||||
import core.game.world.update.flag.context.Animation;
|
||||
import core.tools.Log;
|
||||
import kotlin.Unit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.json.simple.JSONArray;
|
||||
|
|
@ -28,7 +29,7 @@ import core.game.world.GameWorld;
|
|||
import java.awt.*;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static core.api.ContentAPIKt.log;
|
||||
import static core.api.ContentAPIKt.*;
|
||||
import static core.api.regionspec.RegionSpecificationKt.fillWith;
|
||||
import static core.api.regionspec.RegionSpecificationKt.using;
|
||||
|
||||
|
|
@ -197,6 +198,10 @@ public final class HouseManager {
|
|||
player.lock(1);
|
||||
player.sendMessage("House location: " + houseRegion.getBaseLocation() + ", entry: " + getEnterLocation());
|
||||
player.getProperties().setTeleportLocation(getEnterLocation());
|
||||
registerLogoutListener(player, "houselogout", (p) -> {
|
||||
p.setLocation(location.getExitLocation());
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
openLoadInterface(player);
|
||||
checkForAndSpawnServant(player);
|
||||
updateVarbits(player, buildingMode);
|
||||
|
|
@ -249,6 +254,7 @@ public final class HouseManager {
|
|||
*/
|
||||
public static void leave(Player player) {
|
||||
HouseManager house = player.getAttribute("poh_entry", player.getHouseManager());
|
||||
clearLogoutListener(player, "houselogout");
|
||||
if (house.getHouseRegion() == null){
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -490,7 +490,7 @@ public class AIPlayer extends Player {
|
|||
@Override
|
||||
public void clear() {
|
||||
botMapping.remove(uid);
|
||||
super.clear(true);
|
||||
super.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -259,7 +259,12 @@ class ScriptProcessor(val entity: Entity) {
|
|||
targetDestination = when (interactTarget) {
|
||||
is NPC -> DestinationFlag.ENTITY.getDestination(entity, interactTarget)
|
||||
is Scenery -> {
|
||||
val path = Pathfinder.find(entity, interactTarget).points.lastOrNull()
|
||||
val basicPath = Pathfinder.find(entity, interactTarget)
|
||||
val path = basicPath.points.lastOrNull()
|
||||
if (basicPath.isMoveNear) {
|
||||
target.location
|
||||
return
|
||||
}
|
||||
if (path == null) {
|
||||
clearScripts(entity)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -342,26 +342,26 @@ public class Player extends Entity {
|
|||
}
|
||||
super.init();
|
||||
LoginConfiguration.configureLobby(this);
|
||||
setAttribute("logged-in-fully", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
clear(false);
|
||||
if (isArtificial()) {
|
||||
finishClear();
|
||||
return;
|
||||
}
|
||||
Repository.getDisconnectionQueue().remove(getName());
|
||||
Repository.getDisconnectionQueue().add(this, true);
|
||||
details.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the player from the game.
|
||||
* @param force If we should force removal, a player engaged in combat will otherwise remain active until out of combat.
|
||||
* You should NEVER call this manually. This can only be called by the DisconnectionQueue doing its job.
|
||||
* If you think you need to call this manually, you're wrong. Stop. Turn around. Go back. Here be monsters.
|
||||
*/
|
||||
public void clear(boolean force) {
|
||||
if (!isActive())
|
||||
return;
|
||||
if (!force) {
|
||||
Repository.getDisconnectionQueue().remove(getName());
|
||||
Repository.getDisconnectionQueue().add(this, true);
|
||||
details.save();
|
||||
return;
|
||||
}
|
||||
public void finishClear() {
|
||||
if (!isArtificial())
|
||||
GameWorld.getLogoutListeners().forEach((it) -> it.logout(this));
|
||||
setPlaying(false);
|
||||
|
|
@ -647,7 +647,7 @@ public class Player extends Entity {
|
|||
questRepository = new QuestRepository(this);
|
||||
varpManager = new VarpManager(this);
|
||||
new PlayerSaver(this).save();
|
||||
clear(true);
|
||||
clear();
|
||||
return;
|
||||
} else {
|
||||
Repository.sendNews("Hardcore Iron " + gender + " " + this.getUsername() + " has fallen. Total Level: " + this.getSkills().getTotalLevel()); // Not enough room for XP
|
||||
|
|
|
|||
|
|
@ -46,9 +46,7 @@ public final class SceneryBuilder {
|
|||
remove = remove.getWrapper();
|
||||
Scenery current = LandscapeParser.removeScenery(remove);
|
||||
if (current == null) {
|
||||
if (GameWorld.getSettings().isDevMode()) {
|
||||
log(SceneryBuilder.class, Log.ERR, "Object could not be replaced - object to remove is invalid.");
|
||||
}
|
||||
log(SceneryBuilder.class, Log.ERR, "Object could not be replaced with " + construct + " - object to remove is invalid.");
|
||||
return false;
|
||||
}
|
||||
if (current.getRestorePulse() != null) {
|
||||
|
|
@ -121,9 +119,7 @@ public final class SceneryBuilder {
|
|||
remove = remove.getWrapper();
|
||||
Scenery current = LandscapeParser.removeScenery(remove);
|
||||
if (current == null) {
|
||||
if (GameWorld.getSettings().isDevMode()) {
|
||||
log(SceneryBuilder.class, Log.ERR, "Object could not be replaced - object to remove is invalid.");
|
||||
}
|
||||
log(SceneryBuilder.class, Log.ERR, "Object could not be replaced with " + construct + " - object to remove is invalid.");
|
||||
return false;
|
||||
}
|
||||
if (current.getRestorePulse() != null) {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ class DevelopmentCommandSet : CommandSet(Privilege.ADMIN) {
|
|||
val farmKitItems = arrayListOf(Items.RAKE_5341, Items.SPADE_952, Items.SEED_DIBBER_5343, Items.WATERING_CAN8_5340, Items.SECATEURS_5329, Items.GARDENING_TROWEL_5325)
|
||||
val runeKitItems = arrayListOf(Items.AIR_RUNE_556, Items.EARTH_RUNE_557, Items.FIRE_RUNE_554, Items.WATER_RUNE_555, Items.MIND_RUNE_558, Items.BODY_RUNE_559, Items.DEATH_RUNE_560, Items.NATURE_RUNE_561, Items.CHAOS_RUNE_562, Items.LAW_RUNE_563, Items.COSMIC_RUNE_564, Items.BLOOD_RUNE_565, Items.SOUL_RUNE_566, Items.ASTRAL_RUNE_9075)
|
||||
override fun defineCommands() {
|
||||
|
||||
/**
|
||||
* Gives the player a set of tools used to test farming stuff.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class ModerationCommandSet : CommandSet(Privilege.MODERATOR){
|
|||
define("kick", Privilege.MODERATOR){ player, args ->
|
||||
val playerToKick: Player? = Repository.getPlayerByName(args[1])
|
||||
if (playerToKick != null) {
|
||||
playerToKick.clear(true)
|
||||
playerToKick.clear()
|
||||
notify(player, "Player ${playerToKick.username} was kicked.")
|
||||
} else {
|
||||
reject(player, "ERROR REMOVING PLAYER.")
|
||||
|
|
@ -81,7 +81,7 @@ class ModerationCommandSet : CommandSet(Privilege.MODERATOR){
|
|||
}
|
||||
|
||||
playerToKick?.details?.accountInfo?.banEndTime = System.currentTimeMillis() + durationMillis
|
||||
playerToKick?.clear(true)
|
||||
playerToKick?.clear()
|
||||
GameWorld.Pulser.submit(object : Pulse(2) {
|
||||
override fun pulse(): Boolean {
|
||||
val info = GameWorld.accountStorage.getAccountInfo(name)
|
||||
|
|
@ -131,7 +131,7 @@ class ModerationCommandSet : CommandSet(Privilege.MODERATOR){
|
|||
for (p in playersToBan) {
|
||||
val playerToKick = Repository.getPlayerByName(p)
|
||||
playerToKick?.details?.accountInfo?.banEndTime = System.currentTimeMillis() + durationMillis
|
||||
playerToKick?.clear(true)
|
||||
playerToKick?.clear()
|
||||
GameWorld.Pulser.submit(object : Pulse(2) {
|
||||
override fun pulse(): Boolean {
|
||||
val info = GameWorld.accountStorage.getAccountInfo(p)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class GlobalChat : Commands {
|
|||
}
|
||||
|
||||
private fun prepare(sender: String, message: String, isResizable: Boolean): String {
|
||||
val baseColor = if (isResizable) "%G" else "%7512ff"
|
||||
val baseColor = if (isResizable) "%f1b04c" else "%7512ff"
|
||||
val bracketColor = if (isResizable) "%ffffff" else "%000000"
|
||||
return colorize("$bracketColor[${baseColor}G$bracketColor] $sender: ${baseColor}$message")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,13 +18,14 @@ class DisconnectionQueue {
|
|||
/**A
|
||||
* The pending disconnections queue.
|
||||
*/
|
||||
private val queue: MutableMap<String, DisconnectionEntry?>
|
||||
private val queue = HashMap<String, DisconnectionEntry?>()
|
||||
private val queueTimers = HashMap<String, Int>()
|
||||
|
||||
/**
|
||||
* Updates all entries.
|
||||
*/
|
||||
fun update() {
|
||||
if (queue.isEmpty() || GameWorld.ticks % 3 != 0 && GameWorld.settings?.isDevMode != true) {
|
||||
if (queue.isEmpty() || GameWorld.ticks % 3 != 0) {
|
||||
return
|
||||
}
|
||||
//make a copy of current entries as to avoid concurrency exceptions
|
||||
|
|
@ -33,6 +34,18 @@ class DisconnectionQueue {
|
|||
//loop through entries and disconnect each
|
||||
entries.forEach {
|
||||
if(finish(it.value,false)) queue.remove(it.key)
|
||||
else {
|
||||
//Make sure there's no room for the disconnection queue to stroke out and leave someone logged in for 10 years.
|
||||
queueTimers[it.key] = (queueTimers[it.key] ?: 0) + 3
|
||||
if ((queueTimers[it.key] ?: Int.MAX_VALUE) >= 1500) {
|
||||
it.value?.player?.let { player ->
|
||||
player.finishClear()
|
||||
Repository.removePlayer(player)
|
||||
remove(it.key)
|
||||
log(this::class.java, Log.WARN, "Force-clearing ${it.key} after 15 minutes of being in the disconnection queue!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,12 +64,8 @@ class DisconnectionQueue {
|
|||
if (!force && !player.allowRemoval()) {
|
||||
return false
|
||||
}
|
||||
if (entry.isClear) {
|
||||
log(this::class.java, Log.FINE, "Clearing player...")
|
||||
player.clear(true)
|
||||
}
|
||||
player.finishClear()
|
||||
Repository.removePlayer(player)
|
||||
log(this::class.java, Log.INFO, "Player cleared. Removed ${player.details.username}")
|
||||
try {
|
||||
if(player.communication.clan != null)
|
||||
player.communication.clan.leave(player, false)
|
||||
|
|
@ -71,9 +80,11 @@ class DisconnectionQueue {
|
|||
Thread.currentThread().name = "PlayerSave SQL"
|
||||
save(player, true)
|
||||
}
|
||||
log(this::class.java, Log.INFO, "Player cleared. Removed ${player.details.username}.")
|
||||
return true
|
||||
}
|
||||
save(player, false)
|
||||
log(this::class.java, Log.INFO, "Player cleared. Removed ${player.details.username}.")
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -97,94 +108,23 @@ class DisconnectionQueue {
|
|||
queue.clear()
|
||||
}
|
||||
|
||||
fun safeClear(){
|
||||
for(entry in queue.values){
|
||||
finish(entry,false)
|
||||
}
|
||||
queue.clear()
|
||||
}
|
||||
/**
|
||||
* Adds a player to the disconnection queue.
|
||||
* @param player The player.
|
||||
* @param clear If the player should be cleared.
|
||||
*/
|
||||
/**
|
||||
* Adds a player to the disconnection queue.
|
||||
* @param player The player.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun add(player: Player, clear: Boolean = false) {
|
||||
if(queue[player.name] != null) return
|
||||
queue[player.name] = DisconnectionEntry(player, clear)
|
||||
log(this::class.java, Log.INFO, "Queueing ${player.name} for disconnection.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the queue contains the player name.
|
||||
* @param name The name.
|
||||
* @return `True` if so.
|
||||
*/
|
||||
operator fun contains(name: String?): Boolean {
|
||||
return queue.containsKey(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a queued player.
|
||||
* @param name The name.
|
||||
*/
|
||||
fun remove(name: String?) {
|
||||
queue.remove(name)
|
||||
queueTimers.remove(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an entry in the disconnection queue, holding the disconnected
|
||||
* player and time stamp of disconnection.
|
||||
* @author Emperor
|
||||
*/
|
||||
internal inner class DisconnectionEntry(
|
||||
/**
|
||||
* The player.
|
||||
*/
|
||||
val player: Player,
|
||||
/**
|
||||
* If the `Player#clear()` method should be called.
|
||||
*/
|
||||
var isClear: Boolean) {
|
||||
/**
|
||||
* Gets the timeStamp.
|
||||
* @return The timeStamp.
|
||||
*/
|
||||
/**
|
||||
* Sets the timeStamp.
|
||||
* @param timeStamp The timeStamp to set.
|
||||
*/
|
||||
/**
|
||||
* The time of disconnection.
|
||||
*/
|
||||
var timeStamp: Int
|
||||
|
||||
/**
|
||||
* Gets the player.
|
||||
* @return The player.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets the clear.
|
||||
* @return The clear.
|
||||
*/
|
||||
/**
|
||||
* Sets the clear.
|
||||
* @param isClear The clear to set.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructs a new `DisconnectionQueue` `Object`.
|
||||
* @param player The disconnecting player.
|
||||
* @param clear If the player should be cleared.
|
||||
*/
|
||||
init {
|
||||
timeStamp = GameWorld.ticks
|
||||
}
|
||||
}
|
||||
internal data class DisconnectionEntry(val player: Player, var isClear: Boolean) {}
|
||||
|
||||
/**
|
||||
* Saves the player.
|
||||
|
|
@ -199,10 +139,4 @@ class DisconnectionQueue {
|
|||
}
|
||||
return false
|
||||
}
|
||||
/**
|
||||
* Constructs a new `DisconnectionQueue` `Object`.
|
||||
*/
|
||||
init {
|
||||
queue = ConcurrentHashMap()
|
||||
}
|
||||
}
|
||||
|
|
@ -142,8 +142,7 @@ object Repository {
|
|||
if (players[i].details.uid == player.details.uid) {
|
||||
val oldPl = players[i]
|
||||
players.remove(oldPl)
|
||||
oldPl.clear(true)
|
||||
oldPl.session.disconnect()
|
||||
oldPl.clear()
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,8 +61,8 @@ public class IoEventHandler {
|
|||
IoSession session = (IoSession) key.attachment();
|
||||
try {
|
||||
if (channel.read(buffer) == -1) {
|
||||
if (session != null && session.getPlayer() != null) {
|
||||
Repository.getDisconnectionQueue().add(session.getPlayer(), false);
|
||||
if (session != null) {
|
||||
session.disconnect();
|
||||
}
|
||||
key.cancel();
|
||||
return;
|
||||
|
|
@ -70,8 +70,6 @@ public class IoEventHandler {
|
|||
} catch (IOException e) {
|
||||
if (e.getMessage().contains("reset by peer") && session != null) {
|
||||
session.disconnect();
|
||||
if (session.getPlayer() != null)
|
||||
Repository.getDisconnectionQueue().add(session.getPlayer(), false);
|
||||
} else {
|
||||
key.cancel();
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -216,17 +216,12 @@ public class IoSession {
|
|||
key.cancel();
|
||||
SocketChannel channel = (SocketChannel) key.channel();
|
||||
channel.socket().close();
|
||||
if (object instanceof Player) {
|
||||
final Player p = getPlayer();
|
||||
GameWorld.getPulser().submit(new Pulse(0) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
if (p.isActive() && !p.getSession().active) {
|
||||
p.clear();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (getPlayer() != null) {
|
||||
try {
|
||||
getPlayer().clear();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
object = null;
|
||||
} catch (IOException e) {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import core.game.world.repository.Repository
|
|||
import core.tools.Log
|
||||
import core.worker.ManagementEvents.publish
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.math.BigInteger
|
||||
import java.nio.BufferUnderflowException
|
||||
import java.nio.ByteBuffer
|
||||
|
|
@ -163,8 +164,6 @@ object Login {
|
|||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
session.disconnect()
|
||||
Repository.removePlayer(player)
|
||||
player.clear(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,14 +36,18 @@ class MajorUpdateWorker {
|
|||
while (running) {
|
||||
val start = System.currentTimeMillis()
|
||||
Server.heartbeat()
|
||||
|
||||
handleTickActions()
|
||||
|
||||
for (player in Repository.players.filter { !it.isArtificial }) {
|
||||
if (System.currentTimeMillis() - player.session.lastPing > 20000L) {
|
||||
player?.details?.session?.disconnect()
|
||||
player?.session?.lastPing = Long.MAX_VALUE
|
||||
player?.clear(true)
|
||||
player?.session?.disconnect()
|
||||
}
|
||||
if (!player.isActive && !Repository.disconnectionQueue.contains(player.name) && player.getAttribute("logged-in-fully", false)) {
|
||||
//if player has somehow been set as inactive without being queued for disconnection, do that now. This is a failsafe, and should not be relied on.
|
||||
//if you made a change, and now this is suddenly getting triggered a lot, your change is probably bad.
|
||||
player?.session?.disconnect()
|
||||
log(MajorUpdateWorker::class.java, Log.WARN, "Manually disconnecting ${player.name} because they were set as inactive without being disconnected. This is bad.")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -82,29 +86,34 @@ class MajorUpdateWorker {
|
|||
}
|
||||
|
||||
fun handleTickActions(skipPulseUpdate: Boolean = false) {
|
||||
PacketProcessor.processQueue()
|
||||
|
||||
//disconnect all players waiting to be disconnected
|
||||
Repository.disconnectionQueue.update()
|
||||
|
||||
if (!skipPulseUpdate) {
|
||||
GameWorld.Pulser.updateAll()
|
||||
}
|
||||
GameWorld.tickListeners.forEach { it.tick() }
|
||||
|
||||
try {
|
||||
PacketProcessor.processQueue()
|
||||
|
||||
//disconnect all players waiting to be disconnected
|
||||
Repository.disconnectionQueue.update()
|
||||
|
||||
if (!skipPulseUpdate) {
|
||||
GameWorld.Pulser.updateAll()
|
||||
}
|
||||
GameWorld.tickListeners.forEach { it.tick() }
|
||||
|
||||
sequence.start()
|
||||
sequence.run()
|
||||
sequence.end()
|
||||
|
||||
//increment global ticks variable
|
||||
GameWorld.pulse()
|
||||
//tick all manager plugins
|
||||
Managers.tick()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
} finally {
|
||||
try {
|
||||
PacketWriteQueue.flush()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
//increment global ticks variable
|
||||
GameWorld.pulse()
|
||||
//tick all manager plugins
|
||||
Managers.tick()
|
||||
|
||||
PacketWriteQueue.flush()
|
||||
}
|
||||
|
||||
fun start() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue