Merge branch 'anti-void-activities' into 'master'

Void recovery added to ActivityPlugin.

See merge request 2009scape/2009scape!2456
This commit is contained in:
Tooze 2026-07-31 16:43:50 +00:00
commit 3ba423fb72
24 changed files with 190 additions and 48 deletions

View file

@ -24,6 +24,7 @@ import java.awt.*;
import static core.api.ContentAPIKt.*;
import static core.api.regionspec.RegionSpecificationKt.fillWith;
import static core.api.regionspec.RegionSpecificationKt.using;
import static core.game.activity.ActivityManager.lastActivity;
/**
@ -33,6 +34,7 @@ import static core.api.regionspec.RegionSpecificationKt.using;
*/
public final class HouseManager {
public static String POH_Activity = "POH-Activity";
/**
* The current region.
*/
@ -215,6 +217,7 @@ public final class HouseManager {
if (house.isInHouse(player)) {
player.animate(Animation.RESET);
player.getProperties().setTeleportLocation(house.location.getExitLocation());
removeAttribute(player,lastActivity);
}
}

View file

@ -12,7 +12,10 @@ import core.game.world.map.Region;
import core.game.system.task.Pulse;
import core.game.world.map.zone.ZoneType;
import static content.global.skill.construction.HouseManager.POH_Activity;
import static core.api.ContentAPIKt.*;
import static core.game.activity.ActivityManager.lastActivity;
import static core.game.activity.ActivityManager.saveLastActivity;
/**
* Handles the player owned house zone.
@ -67,6 +70,7 @@ public final class HouseZone extends MapZone {
public boolean enter(Entity e) {
if (e instanceof Player) {
Player pl = (Player) e;
setAttribute(pl,saveLastActivity,POH_Activity);
if (house == pl.getHouseManager()) {
previousRegion = house.getHouseRegion().getId();
if (house.getDungeonRegion() != null)
@ -74,6 +78,7 @@ public final class HouseZone extends MapZone {
}
registerLogoutListener(pl, "houselogout", (p) -> {
p.setLocation(house.getLocation().getExitLocation());
removeAttribute(p,lastActivity);
return kotlin.Unit.INSTANCE;
});
}
@ -142,13 +147,14 @@ public final class HouseZone extends MapZone {
clearLogoutListener(p, "houselogout");
if (!getAttribute(p, "kidnapped-by-random", false)) {
removeAttribute(p, "/save:original-loc");
removeAttribute(p,lastActivity);
}
return true;
}
return true;
}
private void remove_items(Player p) {
public static void remove_items(Player p) {
for (int item = Items.KETTLE_7688; item <= Items.CHEFS_DELIGHT_7755; item++) {//Removes all PoH versions of tea and beer barrel-related items
removeAll(p, item, Container.INVENTORY);
removeAll(p, item, Container.BoB);

View file

@ -221,7 +221,7 @@ public final class SummoningTrainingRoom extends OptionHandler {
@Override
public Location getSpawnLocation() {
return null;
return Location.create(2924, 3442, 0);
}
@Override

View file

@ -1,6 +1,7 @@
package content.minigame.fishingtrawler
import core.api.MapArea
import core.api.removeAttribute
import core.game.node.entity.player.Player
import core.game.system.task.Pulse
import core.game.world.GameWorld
@ -10,6 +11,7 @@ import core.game.world.map.zone.ZoneRestriction
import core.plugin.Initializable
import core.tools.ticksToSeconds
import core.game.activity.ActivityManager
import core.game.activity.ActivityManager.lastActivity
import core.game.activity.ActivityPlugin
import core.game.world.map.zone.ZoneBorders
import core.tools.colorize
@ -66,6 +68,7 @@ class FishingTrawlerActivity : ActivityPlugin("fishing trawler",false,false,true
override fun start(player: Player?, login: Boolean, vararg args: Any?): Boolean {
player ?: return false
waitingPlayers.add(player)
super.start(player, login, *args)
return true
}
@ -74,11 +77,13 @@ class FishingTrawlerActivity : ActivityPlugin("fishing trawler",false,false,true
nextStart = GameWorld.ticks + WAIT_TIME
player.dialogueInterpreter.sendDialogue("Trawler will leave in 2 minutes.","If you have a team get them on board now!")
}
waitingPlayers.add(player)
start(player,true)
}
fun removePlayer(player: Player){
waitingPlayers.remove(player)
removeAttribute(player,lastActivity)
}
override fun newInstance(p: Player?): ActivityPlugin {

View file

@ -3,6 +3,7 @@ package content.minigame.fishingtrawler
import core.api.MapArea
import core.api.getRegionBorders
import core.api.*
import core.game.activity.ActivityManager.lastActivity
import core.game.component.Component
import core.game.node.entity.Entity
import core.game.node.scenery.Scenery
@ -155,9 +156,10 @@ class FishingTrawlerSession(val activity: FishingTrawlerActivity? = null) : MapA
player.properties.teleportLocation = Location.create(2666, 3162, 0)
player.incrementAttribute("/save:$STATS_BASE:$FISHING_TRAWLER_GAMES_WON")
val rolls = ceil(session.fishAmount / session.players.size.toDouble()).toInt()
player.removeAttribute("ft-session")
player.setAttribute("/save:ft-rolls", rolls)
removeAttribute(player,"ft-session")
setAttribute(player, "/save:ft-rolls", rolls)
clearLogoutListener(player, "ft-logout")
removeAttribute(player,lastActivity)
}
session.zone.unregister(getRegionBorders(session.region.id))
}

View file

@ -30,6 +30,7 @@ import core.tools.RandomFunction;
import core.tools.StringUtils;
import static core.api.ContentAPIKt.*;
import static core.game.activity.ActivityManager.lastActivity;
/**
@ -252,9 +253,21 @@ public final class PestControlActivityPlugin extends ActivityPlugin {
}
waitingPlayers.add(p);
openLanderInterface(p);
super.start(p, login, args);
return true;
}
/**
* Recovers the player from void should they get left there.
* Is called during login.
* @param player The player.
*/
@Override
public void recover(Player player) {
player.setLocation(getLeaveLocation());
removeAttribute(player,lastActivity);
}
/**
* Updates the lander interface.
* @param p The player.

View file

@ -77,7 +77,7 @@ public final class GDiplomacyCutscene extends CutscenePlugin {
@Override
public Location getSpawnLocation() {
return null;
return Location.create(2958, 3513, 0);
}
@Override

View file

@ -578,7 +578,10 @@ public final class TrollheimPlugin extends OptionHandler {
* Represents the troll location.
*/
private static final Location TROLL_LOCATION = new Location(2851, 3598, 0);
@Override
public void recover(Player p){
//no recovery action needed.
}
/**
* Constructs a new {@code WarningCutscene} {@code Object}.
*/

View file

@ -178,7 +178,7 @@ public class FishingContestCutscene extends CutscenePlugin {
@Override
public Location getSpawnLocation() {
return null;
return Location.create(2639, 3437, 0);
}
@Override

View file

@ -72,6 +72,10 @@ public final class TzhaarFightCavesPlugin extends ActivityPlugin {
e.printStackTrace();
}
}
@Override
public void recover(Player player){
//no recovery needed?
}
@Override
public boolean canLogout(Player p) {

View file

@ -209,7 +209,7 @@ public final class TzhaarFightPitsPlugin extends ActivityPlugin {
if (!login) {
player.setAttribute("fight_pits", true);
ForceMovement.run(player, Location.create(2399, 5177, 0), Location.create(2399, 5175, 0));
return true;
return super.start(player, login, args);
}
if (WAR_ZONE.insideBorder(player.getLocation().getX(), player.getLocation().getY())) {
player.getProperties().setTeleportLocation(Location.create(2399, 5177, 0));

View file

@ -159,7 +159,7 @@ public final class DraynorNodePlugin extends OptionHandler {
@Override
public Location getSpawnLocation() {
return null;
return Location.create(3089, 3252, 1);
}
@Override

View file

@ -78,7 +78,7 @@ public final class OrganCutScene extends CutscenePlugin {
@Override
public Location getSpawnLocation() {
return null;
return Location.create(3242, 3214, 0);
}
@Override

View file

@ -128,7 +128,7 @@ public class SurokMagisDialogue extends DialoguePlugin {
@Override
public Location getSpawnLocation() {
return null;
return Location.create(3209, 3495, 0);
}
@Override

View file

@ -87,6 +87,8 @@ public final class GECutscenePlugin extends CutscenePlugin {
e.printStackTrace();
}
}
@Override
public void recover(Player player) {}
@Override
public Location getSpawnLocation() {

View file

@ -167,7 +167,7 @@ public final class DemonSlayerCutscene extends CutscenePlugin {
@Override
public Location getSpawnLocation() {
return null;
return Location.create(3221, 3373, 0);
}
@Override

View file

@ -291,7 +291,7 @@ public class WLBelowCutscene extends CutscenePlugin {
@Override
public Location getSpawnLocation() {
return null;
return Location.create(3209, 3495, 0);
}
@Override

View file

@ -525,7 +525,7 @@ public class BorkNPC extends AbstractNPC {
@Override
public Location getSpawnLocation() {
return null;
return Location.create(3143, 5545, 0);
}
@Override

View file

@ -1660,6 +1660,41 @@ fun runWorldTask(task: () -> Unit): Pulse {
return pulse
}
/**
* Attribute key containing the player's location before entering an instanced activity.
*/
const val ORIGINAL_LOC = "original-loc"
/**
* Persistent save key for the player's original location.
*/
const val SAVE_ORIGINAL_LOC = "/save:$ORIGINAL_LOC"
/**
* Gets the player's original location before they entered an instanced activity / cutscene / AME
*
* @param player The player.
* @return The stored location, or {@code null} if none exists.
*/
fun getOriginalLocation(player: Player): Location? = getAttribute(player, ORIGINAL_LOC, null)
/**
* Sets player's original location before entering an instanced activity / cutscene / AME
*
* @param player The player.
* @param location The location to store.
*/
fun setOriginalLocation(player: Player, location: Location) = setAttribute(player, SAVE_ORIGINAL_LOC, location)
/**
* Removes the player's stored original location.
*
* This should be called once the player has safely left the activity / cutscene / AME
*
* @param player The player.
*/
fun removeOriginalLocation(player: Player) = removeAttribute(player, ORIGINAL_LOC)
/**
* Teleports or "instantly moves" an entity to a given Location object.
* @param entity the entity to move

View file

@ -1,14 +1,16 @@
package core.game.activity;
import content.global.skill.construction.HouseZone;
import core.ServerConstants;
import core.game.node.entity.player.Player;
import core.tools.Log;
import core.tools.SystemLogger;
import core.game.world.GameWorld;
import java.util.HashMap;
import java.util.Map;
import static core.api.ContentAPIKt.log;
import static content.global.skill.construction.HouseManager.POH_Activity;
import static core.api.ContentAPIKt.*;
/**
* Manages the activities.
@ -21,6 +23,9 @@ public final class ActivityManager {
*/
private static final Map<String, ActivityPlugin> ACTIVITIES = new HashMap<>();
public static String lastActivity = "lastActivity";
public static String saveLastActivity = "/save:"+lastActivity;
/**
* Constructs a new {@code ActivityManager} {@code Object}.
*/
@ -79,4 +84,46 @@ public final class ActivityManager {
public static ActivityPlugin getActivity(String name) {
return ACTIVITIES.get(name);
}
}
@SuppressWarnings("deprecation")
// Intentionally uses the deprecated setLocation: the player must be moved before rendering to prevent client crashes
public static void ActivityRecover(Player player) {
String activityName = getAttribute(player,lastActivity, null);
if (activityName == null) {
int yLoc = player.getLocation().getY();
if (yLoc < 2000) { //credits to PlayerName
sendMessage(player, "You were stuck inside an instance, which should never happen. Please file a bug report, and include the following information: " + player.getLocation() + ". To prevent your client from crashing, you have been teleported back home. We apologise for the inconvenience.");
player.setLocation(ServerConstants.HOME_LOCATION);
}
return;
}
if (activityName.equals(POH_Activity)) {
HouseZone.remove_items(player);
player.setLocation(player.getHouseManager().getLocation().getExitLocation());
removeOriginalLocation(player);
removeAttribute(player,lastActivity);
return;
}
// Fail-safe: don't let a broken activity trap the player.
try {
ActivityPlugin plugin = ACTIVITIES.get(activityName);
if (plugin != null) {
if (plugin.isInstanced()) {
plugin = plugin.newInstance(player);
}
plugin.recover(player);
} else {
sendMessage(player, "You were safely removed from an invalid location.");
sendMessage(player, "Activity: " +activityName+ ", please report this!");
player.setLocation(ServerConstants.HOME_LOCATION);
}
} catch (Throwable t) {
t.printStackTrace();
sendMessage(player, "You were safely removed from an invalid location.");
sendMessage(player, "This activity has something bad or broken!!");
sendMessage(player, "Activity: " +activityName+ ", please report this!");
player.setLocation(ServerConstants.HOME_LOCATION);
}
removeAttribute(player,lastActivity);
}
}

View file

@ -11,6 +11,11 @@ import core.game.world.map.zone.impl.MultiwayCombatZone;
import core.plugin.Plugin;
import core.plugin.PluginManifest;
import core.plugin.PluginType;
import core.tools.Log;
import static core.api.ContentAPIKt.*;
import static core.game.activity.ActivityManager.lastActivity;
import static core.game.activity.ActivityManager.saveLastActivity;
/**
* A plugin implementation used for activity plugins.
@ -130,6 +135,7 @@ public abstract class ActivityPlugin extends MapZone implements Plugin<Player> {
*/
public boolean start(Player player, boolean login, Object... args) {
this.player = player;
setAttribute(player,saveLastActivity, this.getName());
return true;
}
@ -141,7 +147,7 @@ public abstract class ActivityPlugin extends MapZone implements Plugin<Player> {
}
e.getProperties().setSafeZone(safe);
e.getProperties().safeRespawn = this.safeRespawn;
e.setAttribute("activity", this);
setAttribute(e,"activity", this);
return super.enter(e);
}
@ -156,7 +162,8 @@ public abstract class ActivityPlugin extends MapZone implements Plugin<Player> {
}
e.getProperties().setSafeZone(false);
e.getProperties().safeRespawn = ServerConstants.HOME_LOCATION;
e.removeAttribute("activity");
removeAttribute(e,"activity");
removeAttribute(e,lastActivity);
return super.leave(e, logout);
}
@ -165,6 +172,19 @@ public abstract class ActivityPlugin extends MapZone implements Plugin<Player> {
*/
public void register() {
}
// Intentionally uses the deprecated setLocation: the player must be moved before rendering to prevent client crashes
@SuppressWarnings("deprecation")
public void recover(Player player) {
Location spawn = getSpawnLocation();
if (spawn == null) {
String lastActivity = getAttribute(player,core.game.activity.ActivityManager.lastActivity, null);
sendMessage(player, "You were safely removed from an invalid location.");
sendMessage(player, "Activity: " + lastActivity + ", please report this!");
spawn = ServerConstants.HOME_LOCATION;
}
player.setLocation(spawn);
removeAttribute(player,lastActivity);
}
@Override
public Object fireEvent(String identifier, Object... args) {

View file

@ -17,6 +17,10 @@ import core.plugin.PluginType;
import java.util.ArrayList;
import java.util.List;
import static core.api.ContentAPIKt.removeAttribute;
import static core.api.ContentAPIKt.setAttribute;
import static core.game.activity.ActivityManager.lastActivity;
/**
* Represents the plugin used to handle a cutscene.
* @author Vexia
@ -70,9 +74,9 @@ public abstract class CutscenePlugin extends ActivityPlugin {
@Override
public boolean start(final Player player, boolean login, Object... args) {
player.setAttribute("cutscene:original-loc", player.getLocation());
player.removeAttribute("real-end");
player.setAttribute("real-end", player.getLocation());
setAttribute(player,"cutscene:original-loc", player.getLocation());
removeAttribute(player,"real-end");
setAttribute(player,"real-end", player.getLocation());
if (isFade()) {
GameWorld.getPulser().submit(getStartPulse());
} else {
@ -142,6 +146,7 @@ public abstract class CutscenePlugin extends ActivityPlugin {
player.getInterfaceManager().restoreTabs();
player.unlock();// incase he was locked.
player.getWalkingQueue().reset();
removeAttribute(player,lastActivity);
}
/**

View file

@ -7,6 +7,7 @@ import content.global.skill.construction.decoration.pohstorage.StorageState;
import content.global.skill.runecrafting.PouchManager;
import content.global.skill.slayer.SlayerEquipmentFlags;
import core.api.ContentAPIKt;
import core.game.activity.ActivityManager;
import core.game.component.Component;
import core.game.container.Container;
import core.game.container.impl.BankContainer;
@ -345,6 +346,7 @@ public class Player extends Entity {
if (!artificial) {
log(this.getClass(), Log.INFO, getUsername() + " initialising...");
getDetails().getSession().setObject(this);
ActivityManager.ActivityRecover(this);
}
super.init();
LoginConfiguration.configureLobby(this);

View file

@ -33,33 +33,28 @@ class LoginParser(val details: PlayerDetails) {
Repository.removePlayer(player)
flag(AuthResponse.ErrorLoadingProfile)
}
GameWorld.Pulser.submit(object : Pulse(1) {
override fun pulse(): Boolean {
try {
if (details.session.isActive) {
player.properties.spawnLocation = getAttribute(player, "/save:spawnLocation", ServerConstants.HOME_LOCATION)
loginListeners.forEach(Consumer { listener: LoginListener -> listener.login(player) }) //Run our login hooks
parser.runContentHooks() //Run our saved-content-parsing hooks
player.details.session.setObject(player)
player.getDetails().accountInfo.lastUsedIp = player.getDetails().getIpAddress()
if (reconnect) {
reconnect(player)
} else {
flag(AuthResponse.Success)
player.init()
reinitVarps(player)
}
} else {
Repository.removePlayer(player)
}
} catch (t: Throwable) {
t.printStackTrace()
Repository.removePlayer(player)
flag(AuthResponse.ErrorLoadingProfile)
try {
if (details.session.isActive) {
player.properties.spawnLocation = getAttribute(player, "/save:spawnLocation", ServerConstants.HOME_LOCATION)
loginListeners.forEach(Consumer { listener: LoginListener -> listener.login(player) }) //Run our login hooks
parser.runContentHooks() //Run our saved-content-parsing hooks
player.details.session.setObject(player)
player.details.accountInfo.lastUsedIp = player.details.ipAddress
if (reconnect) {
reconnect(player)
} else {
flag(AuthResponse.Success)
player.init()
reinitVarps(player)
}
return true
} else {
Repository.removePlayer(player)
}
})
} catch (t: Throwable) {
t.printStackTrace()
Repository.removePlayer(player)
flag(AuthResponse.ErrorLoadingProfile)
}
}
/**