();
/**
* Constructs a new {@code WalkingQueue} {@code Object}.
@@ -80,29 +74,31 @@ public final class WalkingQueue {
boolean isPlayer = entity instanceof Player;
this.walkDir = -1;
this.runDir = -1;
- if(entity.getLocation() == null) {
+ Location prevLocation = entity.getLocation();
+ if (prevLocation == null) {
return;
}
if (updateTeleport()) {
return;
}
- if (isPlayer && updateRegion(entity.getLocation(), true)) {
+ if (isPlayer && updateRegion(prevLocation, entity.getLocation(), true)) {
+ return;
+ }
+ if (hasTimerActive(entity, "frozen")) {
return;
}
- if (hasTimerActive(entity, "frozen"))
- return;
Point point = walkingQueue.poll();
- boolean drawPath = entity.getAttribute("routedraw", false);
+ boolean drawPath = entity.getAttribute("routedraw", false);
if (point == null) {
updateRunEnergy(false);
- if (isPlayer && drawPath) {
- for (GroundItem item : routeItems) {
- if (item != null) {
- RegionManager.getRegionPlane(item.getLocation()).remove(item);
- }
- }
- routeItems.clear();
- }
+ if (isPlayer && drawPath) {
+ for (GroundItem item : routeItems) {
+ if (item != null) {
+ RegionManager.getRegionChunk(item.getLocation()).remove(item);
+ }
+ }
+ routeItems.clear();
+ }
return;
}
if (isPlayer && ((Player) entity).getSettings().getRunEnergy() < 1.0) {
@@ -174,7 +170,7 @@ public final class WalkingQueue {
}
footPrint = entity.getLocation();
entity.setLocation(dest);
- RegionManager.move(entity);
+ RegionManager.move(entity, prevLocation, dest);
}
this.walkDir = walkDirection;
this.runDir = runDirection;
@@ -190,9 +186,9 @@ public final class WalkingQueue {
if (player.getSettings().getWeight() > 0.0) {
rate *= 1 + (player.getSettings().getWeight() / 100);
}
- if (hasTimerActive(player, "hamstrung")) {
- rate *= 4;
- }
+ if (hasTimerActive(player, "hamstrung")) {
+ rate *= 4;
+ }
return rate;
}
@@ -237,6 +233,7 @@ public final class WalkingQueue {
public boolean updateTeleport() {
if (entity.getProperties().getTeleportLocation() != null) {
reset(false);
+ Location prevLocation = entity.getLocation();
entity.setLocation(entity.getProperties().getTeleportLocation());
entity.getProperties().setTeleportLocation(null);
if (entity instanceof Player) {
@@ -245,13 +242,13 @@ public final class WalkingQueue {
if (last == null) {
last = p.getLocation();
}
- if ((last.getRegionX() - entity.getLocation().getRegionX()) >= 4 || (last.getRegionX() - entity.getLocation().getRegionX()) <= -4) {
- p.getPlayerFlags().setUpdateSceneGraph(true);
- } else if ((last.getRegionY() - entity.getLocation().getRegionY()) >= 4 || (last.getRegionY() - entity.getLocation().getRegionY()) <= -4) {
+ int rx = last.getRegionX(), ry = last.getRegionY();
+ int cx = p.getLocation().getRegionX(), cy = p.getLocation().getRegionY();
+ if (Math.abs(rx - cx) >= 4 || Math.abs(ry - cy) >= 4) {
p.getPlayerFlags().setUpdateSceneGraph(true);
}
}
- RegionManager.move(entity);
+ RegionManager.move(entity, prevLocation, entity.getLocation());
footPrint = entity.getLocation();
entity.getProperties().setTeleporting(true);
return true;
@@ -264,28 +261,19 @@ public final class WalkingQueue {
* return true.
* @return {@code True} if the region updated, {@code false} if not.
*/
- public boolean updateRegion(Location location, boolean move) {
+ public boolean updateRegion(Location prevLocation, Location location, boolean move) {
Player p = (Player) entity;
Location lastRegion = p.getPlayerFlags().getLastSceneGraph();
if (lastRegion == null) {
lastRegion = location;
}
- int rx = lastRegion.getRegionX();
- int ry = lastRegion.getRegionY();
- int cx = location.getRegionX();
- int cy = location.getRegionY();
- if ((rx - cx) >= 4) {
- p.getPlayerFlags().setUpdateSceneGraph(true);
- } else if ((rx - cx) <= -4) {
- p.getPlayerFlags().setUpdateSceneGraph(true);
- }
- if ((ry - cy) >= 4) {
- p.getPlayerFlags().setUpdateSceneGraph(true);
- } else if ((ry - cy) <= -4) {
+ int rx = lastRegion.getRegionX(), ry = lastRegion.getRegionY();
+ int cx = location.getRegionX(), cy = location.getRegionY();
+ if (Math.abs(rx - cx) >= 4 || Math.abs(ry - cy) >= 4) {
p.getPlayerFlags().setUpdateSceneGraph(true);
}
if (move && p.getPlayerFlags().isUpdateSceneGraph()) {
- RegionManager.move(entity);
+ RegionManager.move(entity, prevLocation, location);
return true;
}
return false;
@@ -320,13 +308,13 @@ public final class WalkingQueue {
if (point == null) {
return;
}
- boolean drawRoute = entity.getAttribute("routedraw", false);
- if (drawRoute && entity instanceof Player) {
- Player p = (Player) entity;
- GroundItem item = new GroundItem(new Item(13444), Location.create(x, y, p.getLocation().getZ()), p);
- routeItems.add (item);
- RegionManager.getRegionPlane(item.getLocation()).add(item);
- }
+ boolean drawRoute = entity.getAttribute("routedraw", false);
+ if (drawRoute && entity instanceof Player) {
+ Player p = (Player) entity;
+ GroundItem item = new GroundItem(new Item(13444), Location.create(x, y, p.getLocation().getZ()), p);
+ routeItems.add (item);
+ RegionManager.getRegionChunk(item.getLocation()).add(item);
+ }
int diffX = x - point.getX(), diffY = y - point.getY();
int max = Math.max(Math.abs(diffX), Math.abs(diffY));
for (int i = 0; i < max; i++) {
@@ -363,9 +351,7 @@ public final class WalkingQueue {
/**
* Checks if the entity is running.
- * @return {@code True} if a ctrl + click reward was performed,
the
- * player has the run option enabled or the NPC is a familiar,
- * {@code false} if not.
+ * @return {@code True} if a ctrl + click reward was performed, the player has the run option enabled or the NPC is a familiar, {@code False} if not.
*/
public boolean isRunningBoth() {
if (isRunDisabled()) return false;
@@ -404,7 +390,6 @@ public final class WalkingQueue {
*/
public void reset(boolean running) {
Location loc = entity.getLocation();
-
if (loc == null) {
log(this.getClass(), Log.ERR,
"The entity location provided was null."
diff --git a/Server/src/main/core/game/node/entity/npc/NPC.java b/Server/src/main/core/game/node/entity/npc/NPC.java
index 18334251e..97c1fef8b 100644
--- a/Server/src/main/core/game/node/entity/npc/NPC.java
+++ b/Server/src/main/core/game/node/entity/npc/NPC.java
@@ -142,7 +142,7 @@ public class NPC extends Entity {
public NPCBehavior behavior;
- public boolean isRespawning = false;
+ public boolean isRespawning = false;
/**
* Constructs a new {@code NPC} {@code Object}.
@@ -213,8 +213,8 @@ public class NPC extends Entity {
getProperties().setSpawnLocation(getLocation());
initConfig();
Repository.getNpcs().add(this);
- RegionManager.move(this);
- if (getViewport().getRegion().isActive()) {
+ RegionManager.move(this, null, location);
+ if (getLocation().getRegion().isActive()) {
Repository.addRenderableNPC(this);
}
interactPlugin.setDefault();
@@ -230,9 +230,9 @@ public class NPC extends Entity {
}
}
behavior.onCreation(this);
- // FIXME: hack around MovementPulse's constructor getting run while behavior is null when behavior is set between NPC constructor and init.
+ // FIXME: hack around MovementPulse's constructor getting run while behavior is null when behavior is set between NPC constructor and init.
// FIXME: Commented out as a fix. most npcs were not being able to attack with range/magic due to setting the combat pulse.
- // getProperties().setCombatPulse(new CombatPulse(this));
+ // getProperties().setCombatPulse(new CombatPulse(this));
}
@Override
@@ -240,9 +240,7 @@ public class NPC extends Entity {
super.clear();
Repository.removeRenderableNPC(this);
Repository.getNpcs().remove(this);
- getViewport().setCurrentPlane(null);
behavior.onRemoval(this);
- // getViewport().setRegion(null);
}
/**
@@ -329,7 +327,7 @@ public class NPC extends Entity {
*/
public boolean openShop(Player player) {
if (getName().contains("assistant")) {
- NPC n = RegionManager.getNpc(this, getId() - 1);
+ NPC n = RegionManager.getNpc(location, getId() - 1, 16);
if (n != null) {
return n.openShop(player);
}
@@ -425,7 +423,7 @@ public class NPC extends Entity {
return false;
}
return true;
- }
+ }
@Override
public int getDragonfireProtection(boolean fire) {
@@ -434,7 +432,7 @@ public class NPC extends Entity {
@Override
public void tick() {
- if (!getViewport().getRegion().isActive()) {
+ if (!getLocation().getRegion().isActive()) {
onRegionInactivity();
return;
}
@@ -444,10 +442,10 @@ public class NPC extends Entity {
return;
}
if (isRespawning && respawnTick <= GameWorld.getTicks()) {
- behavior.onRespawn(this);
+ behavior.onRespawn(this);
onRespawn();
- fullRestore();
- isRespawning = false;
+ fullRestore();
+ isRespawning = false;
}
handleTickActions();
super.tick();
@@ -549,10 +547,10 @@ public class NPC extends Entity {
nextWalk = GameWorld.getTicks() + 5 + RandomFunction.randomize(10);
}
- public void resetWalk() {
- nextWalk = GameWorld.getTicks() - 1;
- getWalkingQueue().reset();
- }
+ public void resetWalk() {
+ nextWalk = GameWorld.getTicks() - 1;
+ getWalkingQueue().reset();
+ }
/**
* Called when the region goes inactive.
@@ -570,7 +568,7 @@ public class NPC extends Entity {
}
}
Repository.removeRenderableNPC(this);
- if (getViewport().getRegion() instanceof DynamicRegion) {
+ if (getLocation().getRegion() instanceof DynamicRegion) {
clear();
}
}
@@ -597,7 +595,7 @@ public class NPC extends Entity {
Player p = !(killer instanceof Player) ? null : (Player) killer;
if (p != null) {
p.incrementAttribute("/save:" + STATS_BASE + ":" + STATS_ENEMIES_KILLED);
- PlayerStatsCounter.incrementKills(p, originalId);
+ PlayerStatsCounter.incrementKills(p, originalId);
}
handleDrops(p, killer);
if (!isRespawn())
@@ -608,9 +606,9 @@ public class NPC extends Entity {
setRespawnTick(GameWorld.getTicks() + definition.getConfiguration(NPCConfigParser.RESPAWN_DELAY, 17));
}
- public void setRespawnTicks (int ticks) {
- definition.getHandlers().put(NPCConfigParser.RESPAWN_DELAY, ticks);
- }
+ public void setRespawnTicks (int ticks) {
+ definition.getHandlers().put(NPCConfigParser.RESPAWN_DELAY, ticks);
+ }
@Override
public void commenceDeath(Entity killer) {
@@ -687,9 +685,9 @@ public class NPC extends Entity {
}
getProperties().setAttackStyle(new WeaponInterface.AttackStyle(WeaponInterface.STYLE_CONTROLLED, index));
CombatStyle style = getDefinition().getConfiguration(NPCConfigParser.COMBAT_STYLE);
- if (style != null) {
- getProperties().getCombatPulse().setStyle(style);
- }
+ if (style != null) {
+ getProperties().getCombatPulse().setStyle(style);
+ }
if (style == CombatStyle.MAGIC) {
getProperties().setAutocastSpell(new DefaultCombatSpell(this));
int spell = definition.getConfiguration("spell_id", -1);
@@ -744,10 +742,10 @@ public class NPC extends Entity {
configure();
interactPlugin.setDefault();
if (id == originalId) {
- int ordinal = EntityFlags.getOrdinal (EFlagType.NPC, EntityFlag.TypeSwap);
- getUpdateMasks().unregisterSynced(ordinal);
+ int ordinal = EntityFlags.getOrdinal (EFlagType.NPC, EntityFlag.TypeSwap);
+ getUpdateMasks().unregisterSynced(ordinal);
}
- getUpdateMasks().register(EntityFlag.TypeSwap, id, id != originalId);
+ getUpdateMasks().register(EntityFlag.TypeSwap, id, id != originalId);
return this;
}
@@ -783,10 +781,10 @@ public class NPC extends Entity {
Location returnToSpawnLocation = getProperties().getSpawnLocation().transform(RandomFunction.random(-radius, radius+1), RandomFunction.random(-radius, radius+1), 0);
int dist = (int) Location.getDistance(location, returnToSpawnLocation);
int pathLimit = 14;
- if (dist > pathLimit) {
- Vector normalizedDir = Vector.betweenLocs(this.location, returnToSpawnLocation).normalized();
- returnToSpawnLocation = this.location.transform (normalizedDir.times(pathLimit));
- }
+ if (dist > pathLimit) {
+ Vector normalizedDir = Vector.betweenLocs(this.location, returnToSpawnLocation).normalized();
+ returnToSpawnLocation = this.location.transform (normalizedDir.times(pathLimit));
+ }
return returnToSpawnLocation;
}
Location l = movementPath[movementIndex++];
@@ -1059,5 +1057,4 @@ public class NPC extends Entity {
public void setNeverWalks(boolean neverWalks) {
this.neverWalks = neverWalks;
}
-
}
diff --git a/Server/src/main/core/game/node/entity/npc/agg/AggressiveBehavior.java b/Server/src/main/core/game/node/entity/npc/agg/AggressiveBehavior.java
index 50f93be3f..16153b211 100644
--- a/Server/src/main/core/game/node/entity/npc/agg/AggressiveBehavior.java
+++ b/Server/src/main/core/game/node/entity/npc/agg/AggressiveBehavior.java
@@ -116,7 +116,7 @@ public class AggressiveBehavior {
*/
public List getPossibleTargets(Entity entity, int radius) {
List targets = new ArrayList<>(20);
- for (Player player : RegionManager.getLocalPlayers(entity, radius)) {
+ for (Player player : RegionManager.getLocalPlayers(entity.getLocation(), radius)) {
if (canSelectTarget(entity, player)) {
targets.add(player);
}
diff --git a/Server/src/main/core/game/node/entity/npc/drop/NPCDropTables.java b/Server/src/main/core/game/node/entity/npc/drop/NPCDropTables.java
index baacc117b..951f1d503 100644
--- a/Server/src/main/core/game/node/entity/npc/drop/NPCDropTables.java
+++ b/Server/src/main/core/game/node/entity/npc/drop/NPCDropTables.java
@@ -111,9 +111,9 @@ public final class NPCDropTables {
}
return;
}
- announceIfRare(player, item);
+ announceIfRare(player, item);
if(item.getId() == 6199 && player instanceof Player){
- player.sendMessage("A mystery box has fallen on the ground.");
+ player.sendMessage("A mystery box has fallen on the ground.");
}
sendDropMessage(player, npc.getId(), item);
if (player == null) {
@@ -144,7 +144,7 @@ public final class NPCDropTables {
int itemId = item.getDefinition().isUnnoted() ? item.getId() : item.getNoteChange();
if (player != null && npc.getProperties().isMultiZone() && (item.getDefinition().isTradeable() || item.getName().endsWith("charm")) && player.getCommunication().getClan() != null && player.getCommunication().isLootShare() && player.getCommunication().getLootRequirement().ordinal() >= player.getCommunication().getClan().getLootRequirement().ordinal() && !player.getIronmanManager().isIronman()) {
Player looter = player;
- List players = RegionManager.getLocalPlayers(npc, 16);
+ List players = RegionManager.getLocalPlayers(npc.getLocation(), 16);
List looters = new ArrayList<>(20);
for (Player p : players) {
if (p != null && p.getCommunication().getClan() != null && p.getCommunication().getClan() == player.getCommunication().getClan() && p.getCommunication().isLootShare() && p.getCommunication().getLootRequirement().ordinal() >= p.getCommunication().getClan().getLootRequirement().ordinal()) {
diff --git a/Server/src/main/core/game/node/entity/player/Player.java b/Server/src/main/core/game/node/entity/player/Player.java
index d21c591fc..19233647d 100644
--- a/Server/src/main/core/game/node/entity/player/Player.java
+++ b/Server/src/main/core/game/node/entity/player/Player.java
@@ -131,22 +131,22 @@ public class Player extends Entity {
public Boolean isAfkLogout;
- private StorageState POHStorageState;
+ private StorageState POHStorageState;
- public final Container POHStorageProxyContainer = new POHStorageProxyContainer(this);
+ public final Container POHStorageProxyContainer = new POHStorageProxyContainer(this);
- public StorageState getPOHStorageState() {
- if (POHStorageState == null) {
- POHStorageState = new StorageState(this);
- }
- return POHStorageState;
- }
+ public StorageState getPOHStorageState() {
+ if (POHStorageState == null) {
+ POHStorageState = new StorageState(this);
+ }
+ return POHStorageState;
+ }
- public void setPOHStorageState(StorageState state) {
- this.POHStorageState = state;
- }
+ public void setPOHStorageState(StorageState state) {
+ this.POHStorageState = state;
+ }
- /**
+ /**
* The inventory.
*/
private final Container inventory = new Container(28).register(new InventoryListener(this));
@@ -854,11 +854,10 @@ public class Player extends Entity {
getInterfaceManager().setChatbox(null);
getPulseManager().clear();
getZoneMonitor().getZones().clear();
- getViewport().setCurrentPlane(RegionManager.forId(66666).getPlanes()[3]);
playerFlags.setLastSceneGraph(null);
playerFlags.setUpdateSceneGraph(false);
- playerFlags.setLastViewport(new RegionChunk[Viewport.CHUNK_SIZE][Viewport.CHUNK_SIZE]);
- renderInfo.getLocalNpcs().clear();
+ playerFlags.setLastViewport(new RegionChunk[MapChunkRenderer.BUILD_AREA_SIZE][MapChunkRenderer.BUILD_AREA_SIZE]);
+ renderInfo.getLocalNPCs().clear();
renderInfo.getLocalPlayers().clear();
renderInfo.setLastLocation(null);
renderInfo.setOnFirstCycle(true);
@@ -891,8 +890,8 @@ public class Player extends Entity {
* @param login If the player is logging in.
*/
public void updateSceneGraph(boolean login) {
- Region region = getViewport().getRegion();
- if (region instanceof DynamicRegion || region == null && (region = RegionManager.forId(location.getRegionId())) instanceof DynamicRegion) {
+ Region region = getLocation().getRegion();
+ if (region instanceof DynamicRegion) {
PacketRepository.send(BuildDynamicScene.class, new DynamicSceneContext(this, login));
} else {
PacketRepository.send(UpdateSceneGraph.class, new SceneGraphContext(this, login));
diff --git a/Server/src/main/core/game/node/entity/player/info/RenderInfo.java b/Server/src/main/core/game/node/entity/player/info/RenderInfo.java
index 3aa6a7a07..b72c2fcc7 100644
--- a/Server/src/main/core/game/node/entity/player/info/RenderInfo.java
+++ b/Server/src/main/core/game/node/entity/player/info/RenderInfo.java
@@ -28,7 +28,7 @@ public final class RenderInfo {
/**
* The list of local NPCs.
*/
- private List localNpcs = new LinkedList();
+ private List localNPCs = new LinkedList();
/**
* The appearance time stamps (in millisecond).
@@ -89,16 +89,16 @@ public final class RenderInfo {
* Gets the localNpcs.
* @return The localNpcs.
*/
- public List getLocalNpcs() {
- return localNpcs;
+ public List getLocalNPCs() {
+ return localNPCs;
}
/**
* Sets the localNpcs.
- * @param localNpcs The localNpcs to set.
+ * @param localNPCs The localNpcs to set.
*/
- public void setLocalNpcs(List localNpcs) {
- this.localNpcs = localNpcs;
+ public void setLocalNPCs(List localNPCs) {
+ this.localNPCs = localNPCs;
}
/**
diff --git a/Server/src/main/core/game/node/entity/player/info/login/LoginConfiguration.java b/Server/src/main/core/game/node/entity/player/info/login/LoginConfiguration.java
index fb50ee051..5e6a193ee 100644
--- a/Server/src/main/core/game/node/entity/player/info/login/LoginConfiguration.java
+++ b/Server/src/main/core/game/node/entity/player/info/login/LoginConfiguration.java
@@ -128,13 +128,14 @@ public final class LoginConfiguration {
Repository.getLobbyPlayers().remove(player);
player.setPlaying(true);
UpdateSequence.getRenderablePlayers().add(player);
- RegionManager.move(player);
+ RegionManager.move(player, null, player.getLocation());
player.getMusicPlayer().init();
player.updateAppearance();
player.getPlayerFlags().setUpdateSceneGraph(true);
player.getPacketDispatch().sendInterfaceConfig(226, 1, true);
- if(player.getGlobalData().getTestStage() == 3 && !player.getEmoteManager().isUnlocked(Emotes.SAFETY_FIRST)){
+ if (player.getGlobalData().getTestStage() == 3 && !player.getEmoteManager().isUnlocked(Emotes.SAFETY_FIRST)) {
+ //TODO: port this to save version later. when you do, please also do this for the Explore emote, which has a similar problem (e.g. player_name.json has this).
player.getEmoteManager().unlock(Emotes.SAFETY_FIRST);
}
diff --git a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt
index 351c8dc34..ffac22bf9 100644
--- a/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt
+++ b/Server/src/main/core/game/node/entity/player/info/login/PlayerSaver.kt
@@ -350,7 +350,6 @@ class PlayerSaver (val player: Player){
globalData.put("zafTime",player.savedData.globalData.zafTime.toString())
globalData.put("fritzGlass",player.savedData.globalData.isFritzGlass)
globalData.put("wydinEmployee",player.savedData.globalData.isWydinEmployee)
- globalData.put("draynorRecording",player.savedData.globalData.isDraynorRecording)
globalData.put("geTutorial",player.savedData.globalData.isGeTutorial)
globalData.put("essenceTeleporter",player.savedData.globalData.essenceTeleporter.toString())
globalData.put("recoilDamage",player.savedData.globalData.recoilDamage.toString())
diff --git a/Server/src/main/core/game/node/entity/player/link/GlobalData.java b/Server/src/main/core/game/node/entity/player/link/GlobalData.java
index 62873b245..3565b4309 100644
--- a/Server/src/main/core/game/node/entity/player/link/GlobalData.java
+++ b/Server/src/main/core/game/node/entity/player/link/GlobalData.java
@@ -94,11 +94,6 @@ public final class GlobalData {
*/
private boolean wydinEmployee;
- /**
- * Represents if the draynor recording has been seen.
- */
- private boolean draynorRecording;
-
/**
* Represents if the ge tutorial has been done.
*/
@@ -313,7 +308,6 @@ public final class GlobalData {
zafTime = Long.parseLong(data.get("zafTime").toString());
fritzGlass = (boolean) data.get("fritzGlass");
wydinEmployee = (boolean) data.get("wydinEmployee");
- draynorRecording = (boolean) data.get("draynorRecording");
geTutorial = (boolean) data.get("geTutorial");
essenceTeleporter = Integer.parseInt( data.get("essenceTeleporter").toString());
recoilDamage = Integer.parseInt( data.get("recoilDamage").toString());
@@ -661,22 +655,6 @@ public final class GlobalData {
return zafTime;
}
- /**
- * Gets the draynorRecording.
- * @return The draynorRecording.
- */
- public boolean isDraynorRecording() {
- return draynorRecording;
- }
-
- /**
- * Sets the draynorRecording.
- * @param draynorRecording The draynorRecording to set.
- */
- public void setDraynorRecording(boolean draynorRecording) {
- this.draynorRecording = draynorRecording;
- }
-
/**
* Gets the wydinEmployee.
* @return The wydinEmployee.
diff --git a/Server/src/main/core/game/node/entity/player/link/emote/Emotes.java b/Server/src/main/core/game/node/entity/player/link/emote/Emotes.java
index 2bda2ca57..40ba72ab1 100644
--- a/Server/src/main/core/game/node/entity/player/link/emote/Emotes.java
+++ b/Server/src/main/core/game/node/entity/player/link/emote/Emotes.java
@@ -106,7 +106,7 @@ public enum Emotes {
@Override
public void play(Player player) {
if(player.getLocation().getRegionId() == 13206 && !player.getAttribute("mistag-greeted", false)) {
- RegionManager.getLocalNpcs(player).forEach(npc -> {
+ RegionManager.getLocalNPCs(player.getLocation()).forEach(npc -> {
if (npc.getId() == 2084 && npc.getLocation().withinDistance(player.getLocation(), 3) && player.getQuestRepository().getQuest(Quests.THE_LOST_TRIBE).getStage(player) == 45) {
player.getDialogueInterpreter().open(2084,npc,"greeting");
player.setAttribute("/save:mistag-greeted",true);
@@ -422,4 +422,4 @@ public enum Emotes {
return lockedMessage;
}
-}
\ No newline at end of file
+}
diff --git a/Server/src/main/core/game/node/entity/player/link/prayer/Prayer.java b/Server/src/main/core/game/node/entity/player/link/prayer/Prayer.java
index 8dcdf8265..b11eac8b2 100644
--- a/Server/src/main/core/game/node/entity/player/link/prayer/Prayer.java
+++ b/Server/src/main/core/game/node/entity/player/link/prayer/Prayer.java
@@ -112,15 +112,16 @@ public final class Prayer {
killer.getImpactHandler().manualHit(player, 1 + RandomFunction.randomize(maximum), HitsplatType.NORMAL);
}
if (player.getProperties().isMultiZone()) {
- @SuppressWarnings("rawtypes")
- List targets = null;
+ List extends Entity> targets;
if (killer instanceof NPC) {
- targets = RegionManager.getSurroundingNPCs(player, player, killer);
+ targets = RegionManager.getLocalNPCs(player.getLocation(), 1);
} else {
- targets = RegionManager.getSurroundingPlayers(player, player, killer);
+ targets = RegionManager.getLocalPlayers(player.getLocation(), 1);
}
- for (Object o : targets) {
- Entity entity = (Entity) o;
+ for (Entity entity : targets) {
+ if (entity == player || entity == killer) {
+ continue;
+ }
if (entity.isAttackable(player, CombatStyle.MAGIC, false)) {
entity.getImpactHandler().manualHit(player, 1 + RandomFunction.randomize(maximum), HitsplatType.NORMAL);
}
diff --git a/Server/src/main/core/game/node/entity/skill/Skills.java b/Server/src/main/core/game/node/entity/skill/Skills.java
index 06a978589..0985d1671 100644
--- a/Server/src/main/core/game/node/entity/skill/Skills.java
+++ b/Server/src/main/core/game/node/entity/skill/Skills.java
@@ -41,11 +41,6 @@ public final class Skills {
*/
public double experienceMultiplier = 1.0;
- /**
- * The maximum experience multiplier.
- */
- public static final double MAX_EXPERIENCE_MOD = 60.0;
-
/**
* Represents an array of skill names.
*/
@@ -252,9 +247,9 @@ public final class Skills {
staticLevels[slot] = newLevel;
if (entity instanceof Player) {
- player.updateAppearance();
- LevelUp.levelup(player, slot, amount);
- updateCombatLevel();
+ player.updateAppearance();
+ LevelUp.levelup(player, slot, amount);
+ updateCombatLevel();
}
}
if (entity instanceof Player) {
@@ -288,46 +283,6 @@ public final class Skills {
//Keywords for people ctrl + Fing the project
//xprate xp rate xp multiplier skilling rate
return experienceMultiplier;
- /*if (!(entity instanceof Player)) {
- return 1.0;
- }
- double mod = multiplyer ? (EXPERIENCE_MULTIPLIER) : 1;
- Player p = (Player) entity;
- if (p.getIronmanManager().getMode() == IronmanMode.ULTIMATE) {
- mod /= 4;
- } else if (p.getIronmanManager().getMode() == IronmanMode.STANDARD) {
- mod /= 2;
- }
- //A boost for combat skills that are under level 65.
- if(entity instanceof Player && !this.hasLevel(slot, 65) && isCombat(slot)){
- mod *= 1.5;
- }
- //Grand Exchange region XP boost.
- if(entity.getViewport().getRegion().getRegionId() == 12598){
- mod += 1.5;
- }
- // Pest control, XP halved during the game
- if (entity.getViewport().getRegion().getRegionId() == 10536) {
- mod *= .5;
- }
- if (SystemManager.getSystemConfig().isDoubleExp()) {
- mod *= 2;
- }
- if (HolidayEvent.getCurrent() != null) {
- HolidayEvent.getCurrent().addExperience(p, slot, experience);
- }
- p.getAntiMacroHandler().registerExperience(slot, experience);
- if (TutorialSession.getExtension(p).getStage() < TutorialSession.MAX_STAGE) {
- mod = 1.0;
- } else {
- if (playerMod && p.getExperienceMod() != 0.0) {
- mod *= p.getExperienceMod();
- }
- }
- if (mod > MAX_EXPERIENCE_MOD ) {
- return MAX_EXPERIENCE_MOD;
- }
- return mod;*/
}
/**
@@ -721,9 +676,6 @@ public final class Skills {
if (prayerPoints < 0) {
prayerPoints = 0;
}
- // if (prayerPoints > staticLevels[PRAYER]) {
- // prayerPoints = staticLevels[PRAYER];
- // }
if (entity instanceof Player) {
PacketRepository.send(SkillLevel.class, new SkillContext((Player) entity, PRAYER));
}
diff --git a/Server/src/main/core/game/node/item/GroundItemManager.java b/Server/src/main/core/game/node/item/GroundItemManager.java
index 464dbbe25..98bc91ee3 100644
--- a/Server/src/main/core/game/node/item/GroundItemManager.java
+++ b/Server/src/main/core/game/node/item/GroundItemManager.java
@@ -3,6 +3,7 @@ package core.game.node.item;
import core.game.node.entity.player.Player;
import core.game.world.GameWorld;
import core.game.world.map.Location;
+import core.game.world.map.RegionChunk;
import core.game.world.map.RegionManager;
import core.game.world.update.flag.chunk.ItemUpdateFlag;
import core.net.packet.PacketRepository;
@@ -81,7 +82,7 @@ public final class GroundItemManager {
item.getPlugin().remove(item.getDropper(), item, ItemPlugin.DROP);
}
item.setRemoved(false);
- RegionManager.getRegionPlane(item.getLocation()).add(item);
+ RegionManager.getRegionChunk(item.getLocation()).add(item);
if (GROUND_ITEMS.add(item)) {
return item;
}
@@ -97,7 +98,7 @@ public final class GroundItemManager {
return null;
}
GROUND_ITEMS.remove(item);
- RegionManager.getRegionPlane(item.getLocation()).remove(item);
+ RegionManager.getRegionChunk(item.getLocation()).remove(item);
if (item.isAutoSpawn()) {
item.respawn();
}
@@ -112,7 +113,18 @@ public final class GroundItemManager {
* @return The ground item, or {@code null} if the ground item wasn't found.
*/
public static GroundItem get(int itemId, Location location, Player player) {
- return RegionManager.getRegionPlane(location).getItem(itemId, location, player);
+ RegionChunk chunk = RegionManager.getRegionChunk(location);
+ for (GroundItem item : chunk.getItems()) {
+ if (item.getId() == itemId && location.equals(item.getLocation()) && !item.isRemoved()) {
+ if (!item.isPrivate()) {
+ return item;
+ }
+ if (player != null && item.droppedBy(player)) {
+ return item;
+ }
+ }
+ }
+ return null;
}
/**
@@ -155,7 +167,7 @@ public final class GroundItemManager {
}
}
if (!item.isRemoved()) {
- RegionManager.getRegionPlane(item.getLocation()).remove(item);
+ RegionManager.getRegionChunk(item.getLocation()).remove(item);
}
} else if (!item.isRemainPrivate() && item.getDecayTime() - GameWorld.getTicks() == 100) {
RegionManager.getRegionChunk(item.getLocation()).flag(new ItemUpdateFlag(item, ItemUpdateFlag.CONSTRUCT_TYPE));
diff --git a/Server/src/main/core/game/node/scenery/SceneryBuilder.java b/Server/src/main/core/game/node/scenery/SceneryBuilder.java
index 3dac14bf0..db45f8942 100644
--- a/Server/src/main/core/game/node/scenery/SceneryBuilder.java
+++ b/Server/src/main/core/game/node/scenery/SceneryBuilder.java
@@ -15,15 +15,12 @@ import static core.api.ContentAPIKt.log;
/**
* An aiding class for object constructing/removing.
- *
* @author Emperor
*/
public final class SceneryBuilder {
-
/**
* Replaces a scenery.
- *
- * @param remove The object to remove.
+ * @param remove The object to remove.
* @param construct The object to add.
* @return {@code True} if successful.
*/
@@ -34,9 +31,9 @@ public final class SceneryBuilder {
/**
* Replaces a scenery.
*
- * @param remove The object to remove.
+ * @param remove The object to remove.
* @param construct The object to add.
- * @param clip If clipping should be adjusted.
+ * @param clip If clipping should be adjusted.
* @return {@code True} if successful.
*/
public static boolean replace(Scenery remove, Scenery construct, boolean clip, boolean permanent) {
@@ -71,9 +68,8 @@ public final class SceneryBuilder {
/**
* Replaces the object client sided alone.
- *
- * @param remove The object to remove.
- * @param construct The object to replace with.
+ * @param remove The object to remove.
+ * @param construct The object to replace with.
* @param restoreTicks The restoration ticks.
* @return {@code True} if successful.
*/
@@ -93,9 +89,8 @@ public final class SceneryBuilder {
/**
* Replaces a scenery temporarily.
- *
- * @param remove The object to remove.
- * @param construct The object to add.
+ * @param remove The object to remove.
+ * @param construct The object to add.
* @param restoreTicks The amount of ticks before the object gets restored.
* @return {@code True} if successful.
*/
@@ -105,9 +100,8 @@ public final class SceneryBuilder {
/**
* Replaces a scenery temporarily.
- *
- * @param remove The object to remove.
- * @param construct The object to add.
+ * @param remove The object to remove.
+ * @param construct The object to add.
* @param restoreTicks The amount of ticks before the object gets restored.
* @return {@code True} if successful.
*/
@@ -151,7 +145,6 @@ public final class SceneryBuilder {
/**
* Adds a scenery.
- *
* @param object The object to add.
* @return {@code True} if successful.
*/
@@ -161,10 +154,8 @@ public final class SceneryBuilder {
/**
* Adds a scenery.
- *
* @param object The object to add.
- * @param ticks The amount of ticks this object should last for (-1 for
- * permanent).
+ * @param ticks The amount of ticks this object should last for (-1 for permanent).
* @return {@code True} if successful.
*/
public static Constructed add(Scenery object, int ticks, final GroundItem... items) {
@@ -189,32 +180,8 @@ public final class SceneryBuilder {
return constructed;
}
- /**
- * Removes all objects within a box
- * @param objectId - the object id to remove
- * @param southWest
- * @param northEast
- * @return
- */
- public static boolean removeAll(int objectId, Location southWest, Location northEast) {
- if (southWest.getX() > northEast.getX() || southWest.getY() > northEast.getY())
- return false;
-
- int differenceX = northEast.getX() - southWest.getX();
- int differenceY = northEast.getY() - southWest.getY();
-
- for (int x = 0; x <= differenceX; x++) {
- for (int y = 0; y <= differenceY; y++){
- Scenery object = new Scenery(objectId, Location.create(southWest.getX() + x, southWest.getY() + y, southWest.getZ()));
- remove(object);
- }
- }
- return true;
- }
-
/**
* Removes a scenery.
- *
* @param object The object to remove.
* @return {@code True} if successful.
*/
@@ -233,21 +200,18 @@ public final class SceneryBuilder {
/**
* Removes a scenery.
- *
- * @param object the object.
+ * @param object the object.
* @param respawnTicks the respawn ticks.
* @return {@code True}if removed.
*/
public static boolean remove(final Scenery object, int respawnTicks) {
if (remove(object)) {
GameWorld.getPulser().submit(new Pulse(respawnTicks) {
-
@Override
public boolean pulse() {
add(object);
return true;
}
-
});
return true;
}
@@ -256,8 +220,7 @@ public final class SceneryBuilder {
/**
* Updates the scenery on all the player's screen.
- *
- * @param objects The scenerys.
+ * @param objects The sceneries.
*/
public static void update(Scenery... objects) {
for (Scenery o : objects) {
diff --git a/Server/src/main/core/game/system/command/MapDumpCommand.kt b/Server/src/main/core/game/system/command/MapDumpCommand.kt
index 2743af899..a837ba4a1 100644
--- a/Server/src/main/core/game/system/command/MapDumpCommand.kt
+++ b/Server/src/main/core/game/system/command/MapDumpCommand.kt
@@ -1,7 +1,6 @@
package core.game.system.command
import core.game.node.entity.player.Player
-import core.game.system.command.CommandSet
import core.game.world.map.Location
import core.game.world.map.RegionManager
import core.plugin.Initializable
@@ -34,7 +33,7 @@ class MapDumpCommand : CommandPlugin() {
for (x in 0 until xmax - 1) {
for (y in 0 until ymax - 1) {
for (z in 0 until zmax - 1) {
- val temp = RegionManager.getObject(z, x, y)
+ val temp = RegionManager.getObject(x, y, z)
if (temp != null) {
GameObjectMap[Location(x, y, z)] = temp.id
}
diff --git a/Server/src/main/core/game/system/command/rottenpotato/RottenPotatoExtraDialogue.kt b/Server/src/main/core/game/system/command/rottenpotato/RottenPotatoExtraDialogue.kt
index 5528ecf20..8281ef994 100644
--- a/Server/src/main/core/game/system/command/rottenpotato/RottenPotatoExtraDialogue.kt
+++ b/Server/src/main/core/game/system/command/rottenpotato/RottenPotatoExtraDialogue.kt
@@ -62,7 +62,7 @@ class RottenPotatoExtraDialogue(player: Player? = null) : core.game.dialogue.Dia
end()
sendInputDialogue(player, InputType.STRING_LONG,"Enter the chat message:"){ value ->
val msg = value as String
- RegionManager.getLocalNpcs(player).forEach {
+ RegionManager.getLocalNPCs(player.location).forEach {
it.sendChat(msg)
}
}
@@ -71,7 +71,7 @@ class RottenPotatoExtraDialogue(player: Player? = null) : core.game.dialogue.Dia
//Kill all nearby NPCs
5 -> {
end()
- RegionManager.getLocalNpcs(player).forEach {
+ RegionManager.getLocalNPCs(player.location).forEach {
it.finalizeDeath(player)
}
}
diff --git a/Server/src/main/core/game/system/command/sets/DevelopmentCommandSet.kt b/Server/src/main/core/game/system/command/sets/DevelopmentCommandSet.kt
index 25ef32fef..43a092aab 100644
--- a/Server/src/main/core/game/system/command/sets/DevelopmentCommandSet.kt
+++ b/Server/src/main/core/game/system/command/sets/DevelopmentCommandSet.kt
@@ -31,7 +31,7 @@ import core.tools.Log
import core.game.node.entity.player.info.Rights
import core.game.node.entity.skill.Skills
import core.game.world.map.Location
-import core.game.world.map.RegionManager.getLocalEntitys
+import core.game.world.map.RegionManager
import core.game.world.repository.Repository
import org.json.simple.JSONArray
import kotlin.collections.set
@@ -43,6 +43,7 @@ import content.global.activity.penguinhns.PenguinManager.Companion.spawner
import content.global.activity.penguinhns.PenguinManager.Companion.tagMapping
import content.global.activity.penguinhns.PenguinManager.Companion.updateStoreFile
import core.ServerStore.Companion.toJSONArray
+import core.game.world.map.RegionManager.getLocalEntities
import org.rs09.consts.NPCs
@Initializable
@@ -120,7 +121,7 @@ class DevelopmentCommandSet : CommandSet(Privilege.ADMIN) {
return@define
}
- define("cs2", Privilege.ADMIN, "::cs2 id args", "Allows you to call arbitrary cs2 scripts during runtime") {player, args ->
+ define("cs2", Privilege.ADMIN, "::cs2 id args", "Allows you to call arbitrary cs2 scripts during runtime") {player, args ->
var scriptArgs = ArrayList()
if (args.size == 2) {
runcs2(player, args[1].toIntOrNull() ?: return@define)
@@ -155,10 +156,6 @@ class DevelopmentCommandSet : CommandSet(Privilege.ADMIN) {
sendMessage(player, "Job cleared successfully.")
}
- define("region", Privilege.STANDARD, "", "Prints your current Region ID.") {player, args ->
- sendMessage(player, "Region ID: ${player.viewport.region.regionId}")
- }
-
define("spellbook", Privilege.ADMIN, "::spellbook book ID (0 = MODERN, 1 = ANCIENTS, 2 = LUNARS)", "Swaps your spellbook to the given book ID."){player, args ->
if(args.size < 2){
reject(player,"Usage: ::spellbook [int]. 0 = MODERN, 1 = ANCIENTS, 2 = LUNARS")
@@ -460,31 +457,6 @@ class DevelopmentCommandSet : CommandSet(Privilege.ADMIN) {
target.skills.addExperience(skill, xp!!)
}
- define("renewpenguins", Privilege.ADMIN, "", "Generates a fresh set of weekly penguins") { player, _ ->
- val spawnedOrdinals = (PenguinHNSEvent.getStoreFile()["spawned-penguins"] as JSONArray).map { it.toString().toInt() }
- val penguinNPCs = arrayListOf(NPCs.BARREL_8104, NPCs.BUSH_8105,NPCs.CACTUS_8107,NPCs.CRATE_8108,NPCs.ROCK_8109,NPCs.TOADSTOOL_8110)
-
- spawnedOrdinals.forEach {
- val peng = Penguin.values()[it]
- val nearNPCs = getLocalEntitys(peng.location,1)
- nearNPCs.forEach { npc ->
- if (npc.id in penguinNPCs) {
- poofClear(npc as NPC)
- }
- }
- }
- penguins = spawner.spawnPenguins(10)
- PenguinHNSEvent.getStoreFile()["spawned-penguins"] = penguins.toJSONArray()
- tagMapping.clear()
- for (p in penguins) {
- tagMapping.put(p, JSONArray())
- val pengCoord = Penguin.values()[p].location
- player.debug("Penguin spawned at:$pengCoord")
- }
- updateStoreFile()
- player.debug("Penguin positions have been renewed")
- }
-
define("spawnpenguin",Privilege.ADMIN,"::spawnPenguin Ordinal","Adds a new Penguin spawn to this weeks list based on the ordinal provided 0-64"){player,args->
if (args.size!=2) reject (player,"Usage: ::spawnpenguin Ordinal")
val ordinal = args[1].toIntOrNull()
@@ -507,6 +479,5 @@ class DevelopmentCommandSet : CommandSet(Privilege.ADMIN) {
val pengCoords = peng.location
player.debug("Penguin spawned at:$pengCoords")
}
-
- }
+ }
}
diff --git a/Server/src/main/core/game/system/command/sets/FunCommandSet.kt b/Server/src/main/core/game/system/command/sets/FunCommandSet.kt
index 05b59c153..c65db80df 100644
--- a/Server/src/main/core/game/system/command/sets/FunCommandSet.kt
+++ b/Server/src/main/core/game/system/command/sets/FunCommandSet.kt
@@ -27,7 +27,6 @@ import org.rs09.consts.Sounds
import java.awt.HeadlessException
import java.awt.Toolkit
import java.awt.datatransfer.StringSelection
-import kotlin.streams.toList
@Initializable
class FunCommandSet : CommandSet(Privilege.ADMIN) {
@@ -99,7 +98,7 @@ class FunCommandSet : CommandSet(Privilege.ADMIN) {
if (args.size < 2) {
reject(player, "Syntax error: ::npcanim ")
}
- npcs = RegionManager.getLocalNpcs(player.location, 10)
+ npcs = RegionManager.getLocalNPCs(player.location, 10)
for (n in npcs) {
n.lock(6)
n.faceTemporary(player, 6)
@@ -356,10 +355,13 @@ class FunCommandSet : CommandSet(Privilege.ADMIN) {
if (args.size != 2)
reject(player, "Usage: ::barrage radius[max = 50]")
val radius = if (args[1].toInt() > 50) 50 else args[1].toInt()
- val nearbyPlayers = RegionManager.getLocalPlayers(player, radius).stream().filter { p: Player -> p.username != player.username }.toList()
+ val nearbyPlayers = RegionManager.getLocalPlayers(player.location, radius)
animate(player, 1978)
playGlobalAudio(player.location, Sounds.ICE_CAST_171)
for (p in nearbyPlayers) {
+ if (p.name == player.name) {
+ continue
+ }
playGlobalAudio(p.location, Sounds.ICE_BARRAGE_IMPACT_168, 20)
val impactAmount = if (p.skills.lifepoints < 10 ) 0 else RandomFunction.getRandom(3)
impact(p, impactAmount, ImpactHandler.HitsplatType.NORMAL)
diff --git a/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt b/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt
index 0727f65db..3c1bc2443 100644
--- a/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt
+++ b/Server/src/main/core/game/system/command/sets/MiscCommandSet.kt
@@ -115,7 +115,7 @@ class MiscCommandSet : CommandSet(Privilege.ADMIN){
*/
define("loc", Privilege.STANDARD, "", "Prints quite a lot of information about your current location."){ player, _->
val l = player.location
- val r = player.viewport.region
+ val r = l.region
var obj: Scenery? = null
notify(player,"Absolute: " + l + ", regional: [" + l.localX + ", " + l.localY + "], chunk: [" + l.chunkOffsetX + ", " + l.chunkOffsetY + "], flag: [" + RegionManager.isTeleportPermitted(l) + ", " + RegionManager.getClippingFlag(l) + ", " + RegionManager.isLandscape(l) + "].")
notify(player,"Region: [id=" + l.regionId + ", active=" + r.isActive + ", instanced=" + (r is DynamicRegion) + "], obj=" + RegionManager.getObject(l) + ".")
diff --git a/Server/src/main/core/game/system/command/sets/SpawnCommandSet.kt b/Server/src/main/core/game/system/command/sets/SpawnCommandSet.kt
index 468bdaf96..7649bb414 100644
--- a/Server/src/main/core/game/system/command/sets/SpawnCommandSet.kt
+++ b/Server/src/main/core/game/system/command/sets/SpawnCommandSet.kt
@@ -3,13 +3,17 @@ package core.game.system.command.sets
import core.api.log
import core.api.sendMessage
import core.cache.Cache
-import core.game.node.scenery.Scenery
-import core.game.node.scenery.SceneryBuilder
import core.game.node.entity.npc.NPC
import core.game.node.item.Item
+import core.game.node.scenery.Constructed
+import core.game.node.scenery.Scenery
+import core.game.node.scenery.SceneryBuilder
import core.game.system.command.CommandPlugin
-import core.plugin.Initializable
import core.game.system.command.Privilege
+import core.game.world.map.RegionManager
+import core.game.world.map.RegionManager.getObject
+import core.game.world.map.RegionManager.getRegionChunk
+import core.plugin.Initializable
import core.tools.Log
import java.awt.Toolkit
import java.awt.datatransfer.StringSelection
@@ -142,5 +146,53 @@ class SpawnCommandSet : CommandSet(Privilege.ADMIN){
}
}
}
+
+ define("removeobject", Privilege.ADMIN, "::removeobject", "Removes the first scenery at your current coordinates (the meaning of 'the first' is currently arbitrary, do not use this in production)") { player, args ->
+ if (args.size != 1) reject(player, "::removeobject doesn't support arguments")
+ val obj = getObject(player.location)
+ if (obj == null) {
+ sendMessage(player, "All four objects on the tile were null.")
+ return@define
+ }
+ sendMessage(player, "The first object found on the tile was ${obj.id}; it will now be removed.")
+ SceneryBuilder.remove(obj)
+ }
+
+ define("objects", Privilege.STANDARD, "::objects", "Prints a list of all ten sceneries at your current coordinates") { player, args ->
+ if (args.size != 1) reject(player, "::objects doesn't support arguments")
+ val chunk = getRegionChunk(player.location)
+ fun dump(label: String, objects: List) {
+ sendMessage(player, "--- $label ---")
+ val nulls = ArrayList(10)
+ for ((i, o) in objects.withIndex()) {
+ if (o == null) {
+ nulls.add(i)
+ } else {
+ val c = if (o is Constructed) "C" else ""
+ val r = if (o.isRenderable) "R" else ""
+ val a = if (o.isActive) "A" else ""
+ var props = c + r + a
+ if (props.isNotEmpty()) {
+ props = " *$props"
+ }
+ sendMessage(player, " $i: $o$props")
+ }
+ }
+ if (nulls.isNotEmpty()) {
+ sendMessage(player, " ${nulls.joinToString()}: null")
+ }
+ }
+ val stat = ArrayList(10)
+ for (i in 0 until 4) {
+ val obj = chunk.statObjects[player.location.chunkOffsetX][player.location.chunkOffsetY][i]
+ if (obj != null) {
+ stat.add(obj)
+ }
+ }
+ dump("Chunk static objects", stat)
+ dump("Chunk dynamic objects", chunk.getObjects(player.location.chunkOffsetX, player.location.chunkOffsetY).asList())
+ dump("RegionManager", listOf(getObject(player.location))) //getObject can only get one
+ sendMessage(player, "--- That's all, folks! ---")
+ }
}
}
diff --git a/Server/src/main/core/game/system/command/sets/TeleportCommandSet.kt b/Server/src/main/core/game/system/command/sets/TeleportCommandSet.kt
index 30e4107c9..e8d7b84f3 100644
--- a/Server/src/main/core/game/system/command/sets/TeleportCommandSet.kt
+++ b/Server/src/main/core/game/system/command/sets/TeleportCommandSet.kt
@@ -100,69 +100,6 @@ class TeleportCommandSet : CommandSet(Privilege.ADMIN){
player.properties.teleportLocation = Location.create(args[1].toInt(), args[2].toInt(), if (args.size > 3) args[3].toInt() else 0)
}
- /**
- * Teleport to the first object with the given name in the given regionX regionY
- */
- define("teleobj", Privilege.ADMIN, "::teleobj RX_RY OBJ NAME", "Teleports to the first object with the given name."){player, args ->
- if(args.size < 3) reject(player, "Usage: regionX_regionY Object Name")
- var objName = ""
- for(i in 2 until args.size) objName += (args[i] + if(i + 1 == args.size) "" else " ")
-
- val tokens = args[1].split("_")
- if(tokens.size != 2) reject(player, "Usage: regionX_regionY Object Name")
- val regionX = tokens[0].toInt()
- val regionY = tokens[1].toInt()
-
- val regionId = (regionX shl 8) or regionY
- val region = RegionManager.forId(regionId)
-
- GlobalScope.launch {
- for (plane in region.planes) {
- for (objects in plane.objects.filterNotNull()) {
- for (parent in objects.filterNotNull()) {
- if (parent.childs != null) {
- for (obj in parent.childs.filterNotNull()) {
- if (obj.name.equals(objName, ignoreCase = true)) {
- player.properties.teleportLocation = obj.location
- return@launch
- }
- }
- } else {
- if (parent.name.equals(objName, ignoreCase = true)) {
- player.properties.teleportLocation = parent.location
- return@launch
- }
- }
- }
- }
- }
- }
- }
-
- /**
- * Finds a list of objects/sceneries in a region
- */
- define("findobjs", Privilege.ADMIN, "::findobjs REGION ID SCENERY ID", "Finds all locations of scenery objects of id."){player, args ->
- if(args.size < 4) reject(player, "Usage: region_id scenery_id")
- val regionId = args[1].toInt()
- val sceneryId = args[2].toInt()
- val sceneryIdEnd = args[3].toInt()
-
- val region = RegionManager.forId(regionId)
-
- GlobalScope.launch {
- for (plane in region.planes) {
- for (objects in plane.objects.filterNotNull()) {
- for (parent in objects.filterNotNull()) {
- if (parent.id in sceneryId..sceneryIdEnd) {
- println(parent.location)
- }
- }
- }
- }
- }
- }
-
/**
* Teleport to a specific player
*/
diff --git a/Server/src/main/core/game/system/config/ConfigParser.kt b/Server/src/main/core/game/system/config/ConfigParser.kt
index db7e1b0f3..fd88dfdfa 100644
--- a/Server/src/main/core/game/system/config/ConfigParser.kt
+++ b/Server/src/main/core/game/system/config/ConfigParser.kt
@@ -16,6 +16,7 @@ class ConfigParser : Commands {
ItemConfigParser().load()
ObjectConfigParser().load()
XteaParser().load()
+ ObjectOverrideParser().load()
InterfaceConfigParser().load()
}
fun postPlugin() {
@@ -31,7 +32,12 @@ class ConfigParser : Commands {
}
fun reloadConfigs(callback: () -> Unit) {
- GlobalScope.launch {
+ GlobalScope.launch {
+ RegionManager.apply { r ->
+ r.addSceneries.clear()
+ r.removeSceneries.clear()
+ }
+
Repository.npcs.toTypedArray().forEach { npc ->
npc.isRespawn = false
npc.clear()
@@ -41,7 +47,7 @@ class ConfigParser : Commands {
GroundItemManager.getItems().toTypedArray().forEach {gi ->
GroundItemManager.getItems().remove(gi)
- RegionManager.getRegionPlane(gi.location).remove(gi)
+ RegionManager.getRegionChunk(gi.location).remove(gi)
}
prePlugin()
diff --git a/Server/src/main/core/game/system/config/ObjectOverrideParser.kt b/Server/src/main/core/game/system/config/ObjectOverrideParser.kt
new file mode 100644
index 000000000..c75840039
--- /dev/null
+++ b/Server/src/main/core/game/system/config/ObjectOverrideParser.kt
@@ -0,0 +1,57 @@
+package core.game.system.config
+
+import core.ServerConstants
+import core.api.log
+import core.game.node.scenery.Scenery
+import core.game.world.map.Location
+import core.game.world.map.RegionManager
+import core.tools.Log
+import org.json.simple.JSONArray
+import org.json.simple.JSONObject
+import org.json.simple.parser.JSONParser
+import java.io.FileReader
+
+class ObjectOverrideParser {
+ val parser = JSONParser()
+ var reader: FileReader? = null
+ fun load() {
+ var count = 0
+ reader = FileReader(ServerConstants.CONFIG_PATH + "object_overrides.json")
+ val overridelist = parser.parse(reader) as JSONArray
+ for (entry in overridelist) {
+ val e = entry as JSONObject
+
+ // Mandatory fields
+ val mode = e["mode"].toString()
+ val id = e["id"].toString().toInt()
+ val loc = Location.fromString(e["location"].toString())
+
+ // Optional fields
+ val type = e["type"]?.toString()?.toInt() ?: 10
+ val direction = e["direction"]?.toString() ?: "n"
+
+ // Now create a Scenery object and assign it to its region
+ val rotation = when (direction) {
+ "n" -> 1
+ "ne" -> 2
+ "nw" -> 0
+ "w" -> 3
+ "e" -> 4
+ "sw" -> 5
+ "se" -> 7
+ "s" -> 6
+ else -> 1
+ }
+ val obj = Scenery(id, loc, type, rotation)
+ val region = RegionManager.forId(loc.regionId)
+ when (mode) {
+ "remove" -> region.removeSceneries.add(obj) //type and rotation are currently ignored, but this can be changed in Region.java if needed
+ "add" -> region.addSceneries.add(obj)
+ else -> log(this::class.java, Log.ERR, "Ignored unknown ObjectOverride mode $mode!")
+ }
+ count++
+ }
+ log(this::class.java, Log.FINE, "Parsed $count object overrides.")
+ }
+}
+
diff --git a/Server/src/main/core/game/world/GameWorld.kt b/Server/src/main/core/game/world/GameWorld.kt
index 2c0b55e2c..a7b5e1f0b 100644
--- a/Server/src/main/core/game/world/GameWorld.kt
+++ b/Server/src/main/core/game/world/GameWorld.kt
@@ -18,7 +18,9 @@ import core.ServerStore
import core.auth.AuthProvider
import core.auth.Auth
import core.game.system.config.ConfigParser
+import core.game.world.map.RegionChunk
import core.game.world.repository.Repository
+import core.game.world.update.ChunkUpdateTracker
import core.plugin.ClassScanner
import core.storage.AccountStorageProvider
import core.tools.Log
@@ -164,6 +166,7 @@ object GameWorld {
fun prompt(run: Boolean, directory: String?){
log(GameWorld::class.java, Log.FINE, "Prompting ${settings?.name} Game World...")
Cache.init(ServerConstants.CACHE_PATH)
+ RegionChunk.dirtyListener = ChunkUpdateTracker
//go overboard with checks to make sure dev mode authenticator never triggers on live
Auth.configure()
ConfigParser().prePlugin()
diff --git a/Server/src/main/core/game/world/map/BuildRegionChunk.java b/Server/src/main/core/game/world/map/BuildRegionChunk.java
deleted file mode 100644
index 008937301..000000000
--- a/Server/src/main/core/game/world/map/BuildRegionChunk.java
+++ /dev/null
@@ -1,336 +0,0 @@
-package core.game.world.map;
-
-
-import core.game.node.entity.player.Player;
-import core.game.node.item.GroundItem;
-import core.game.node.item.Item;
-import core.game.node.scenery.Constructed;
-import core.game.node.scenery.Scenery;
-import core.game.node.scenery.SceneryBuilder;
-import core.tools.Log;
-import core.tools.SystemLogger;
-import core.game.world.map.build.LandscapeParser;
-import core.net.packet.IoBuffer;
-import core.net.packet.out.ClearScenery;
-import core.net.packet.out.ConstructGroundItem;
-import core.net.packet.out.ConstructScenery;
-
-import static core.api.ContentAPIKt.log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * A region chunk, used for easily modifying objects.
- * @author Emperor
- *
- */
-public class BuildRegionChunk extends RegionChunk {
-
- /**
- * The maximum amount of objects to be stored on one tile in the chunk.
- */
- public static final int ARRAY_SIZE = 10;
-
- /**
- * The list of changes made.
- */
- private final Scenery[][][] objects;
-
- /**
- * Constructs a new {@code BuildRegionChunk} {@code Object}
- * @param base The base location.
- * @param rotation The rotation.
- * @param plane The region plane.
- */
- public BuildRegionChunk(Location base, int rotation, RegionPlane plane) {
- super(base, rotation, plane);
- this.objects = new Scenery[ARRAY_SIZE][8][8];
- }
-
- public BuildRegionChunk(Location base, int rotation, RegionPlane plane, Scenery[][] objects) {
- this(base, rotation, plane);
- for (int x = 0; x < SIZE; x++) {
- for (int y = 0; y < SIZE; y++) {
- if(objects[x][y] != null) {
- this.objects[0][x][y] = new Scenery(objects[x][y]);
- }
- }
- }
- }
-
- @Override
- protected boolean appendUpdate(Player player, IoBuffer buffer) {
- boolean updated = false;//super.appendUpdate(player, buffer);
- for (int i = 0; i < objects.length; i++) {
- for (int x = 0; x < SIZE; x++) {
- for (int y = 0; y < SIZE; y++) {
- Scenery o = objects[i][x][y];
- if (o instanceof Constructed) {
- ConstructScenery.write(buffer, o);
- updated = true;
- }
- else if (o != null && !o.isRenderable()) {
- ClearScenery.write(buffer, o);
- updated = true;
- }
- }
- }
- }
- ArrayList totalItems = drawItems(items, player);
- for (GroundItem item : totalItems) {
- if (item != null && item.isActive() && item.getLocation() != null) {
- if (!item.isPrivate() || item.droppedBy(player)) {
- ConstructGroundItem.write(buffer, item);
- updated = true;
- }
- }
- }
- return updated;
- }
-
- @Override
- public void rotate(Direction direction) {
- if (rotation != 0) {
- log(this.getClass(), Log.ERR, "Region chunk was already rotated!");
- return;
- }
- Scenery[][][] copy = new Scenery[ARRAY_SIZE][SIZE][SIZE];
- int baseX = currentBase.getLocalX();
- int baseY = currentBase.getLocalY();
- for (int x = 0; x < SIZE; x++) {
- for (int y = 0; y < SIZE; y++) {
- for (int i = 0; i < objects.length; i++) {
- Scenery object = copy[i][x][y] = objects[i][x][y];
- if (object != null) {
- SceneryBuilder.remove(object);
- this.remove(object);
- }
- }
- }
- }
- clear();
- switch(direction) {
- case NORTH: rotation = 0; break;
- case EAST: rotation = 1; break;
- case SOUTH: rotation = 2; break;
- case WEST: rotation = 3; break;
- default: rotation = (direction.toInteger() + (direction.toInteger() % 2 == 0 ? 2 : 0)) % 4;
- log(this.getClass(), Log.ERR, "Attempted to rotate a chunk in a non-cardinal direction - using fallback rotation code. This should be investigated!");
- break;
- };
- for (int i = 0; i < objects.length; i++) {
- for (int x = 0; x < SIZE; x++) {
- for (int y = 0; y < SIZE; y++) {
- Scenery object = copy[i][x][y];
- if (object != null) {
- int[] pos = getRotatedPosition(x, y, object.getDefinition().getSizeX(), object.getDefinition().getSizeY(), object.getRotation(), rotation);
- Scenery obj = object.transform(object.getId(), (object.getRotation() + rotation) % 4, object.getLocation().transform(pos[0] - x, pos[1] - y, 0));
- if (object instanceof Constructed) {
- obj = obj.asConstructed();
- }
- obj.setActive(object.isActive());
- obj.setRenderable(object.isRenderable());
- LandscapeParser.flagScenery(plane, baseX + pos[0], baseY + pos[1], obj, true, true);
- }
- }
- }
- }
- }
-
- @Override
- public BuildRegionChunk copy(RegionPlane plane) {
- BuildRegionChunk chunk = new BuildRegionChunk(base, rotation, plane);
- for (int i = 0; i < chunk.objects.length; i++) {
- for (int x = 0; x < SIZE; x++) {
- for (int y = 0; y < SIZE; y++) {
- Scenery o = objects[i][x][y];
- if (o instanceof Constructed) {
- chunk.objects[i][x][y] = o.transform(o.getId(), o.getRotation()).asConstructed();
- }
- else if (o != null) {
- chunk.objects[i][x][y] = o.transform(o.getId());
- chunk.objects[i][x][y].setActive(o.isActive());
- chunk.objects[i][x][y].setRenderable(o.isRenderable());
- }
- }
- }
- }
- return chunk;
- }
-
- @Override public void rebuildFlags(RegionPlane from) {
- for (int x = 0; x < 8; x++) {
- for (int y = 0; y < 8; y++) {
- Location loc = currentBase.transform(x,y,0);
- Location fromLoc = base.transform(x,y,0);
- plane.getFlags().getLandscape()[loc.getLocalX()][loc.getLocalY()] = from.getFlags().getLandscape()[fromLoc.getLocalX()][fromLoc.getLocalY()];
- plane.getFlags().clearFlag(x, y);
- for (int i = 0; i < ARRAY_SIZE; i++) {
- Scenery obj = objects[i][x][y];
- if (obj != null)
- LandscapeParser.applyClippingFlagsFor(plane, loc.getLocalX(), loc.getLocalY(), obj);
- }
- }
- }
- }
-
- @Override
- public void clear() {
- super.clear();
- for (int i = 0; i < objects.length; i++) {
- for (int x = 0; x < objects[i].length; x++) {
- for (int y = 0; y < objects[i][x].length; y++) {
- objects[i][x][y] = null;
- }
- }
- }
- }
-
- /**
- * Removes the scenery.
- * @param object The object to remove.
- */
- public void remove(Scenery object) {
- int chunkX = object.getLocation().getChunkOffsetX();
- int chunkY = object.getLocation().getChunkOffsetY();
- Scenery current = null;
- int index = -1;
- int i = 0;
- while ((current == null || current.getId() != object.getId()) && i < objects.length) {
- current = objects[i++][chunkX][chunkY];
- if ((current == null || current.getId() < 1) && index == -1) {
- index = i - 1;
- }
- }
- if (current != null && current.equals(object)) {
- current.setActive(false);
- object.setRenderable(false);
- }
- else {
- objects[index][chunkX][chunkY] = object;
- }
- object.setActive(false);
- object.setRenderable(false);
- }
-
- /**
- * Adds the scenery.
- * @param object The object to add.
- */
- public void add(Scenery object) {
- int chunkX = object.getLocation().getChunkOffsetX();
- int chunkY = object.getLocation().getChunkOffsetY();
- Scenery current = null;
- int index = -1;
- int i = 0;
- while ((current == null || current.getId() != object.getId()) && i < objects.length) {
- current = objects[i++][chunkX][chunkY];
- if ((current == null || current.getId() < 1) && index == -1) {
- index = i - 1;
- }
- }
- if (current != null && current.equals(object)) {
- current.setActive(true);
- current.setRenderable(true);
- }
- else if (index == -1) {
- throw new IllegalStateException("Insufficient array length for storing object!");
- }
- else {
- objects[index][chunkX][chunkY] = object = object.asConstructed();
- }
- object.setActive(true);
- object.setRenderable(true);
- }
-
- /**
- * Stores an object on the region chunk.
- * @param object The object.
- */
- public void store(Scenery object) {
- if (object == null) {
- return;
- }
- int chunkX = object.getLocation().getChunkOffsetX();
- int chunkY = object.getLocation().getChunkOffsetY();
- for (int i = 0; i < objects.length; i++) {
- Scenery stat = objects[i][chunkX][chunkY];
- if (stat == null || stat.getId() < 1) {
- objects[i][chunkX][chunkY] = object;
- object.setActive(true);
- object.setRenderable(true);
- return;
- }
- }
- System.err.print("Objects - [");
- for (int i = 0; i < objects.length; i++) {
- System.err.print(objects[i][chunkX][chunkY]);
- if (i < objects.length - 1) {
- System.err.print(", ");
- }
- }
- log(this.getClass(), Log.ERR, "]!");
- throw new IllegalStateException("Insufficient array length for storing all objects! ");
- }
-
- /**
- * Gets the objects index for the given object id.
- * @param x The x-coordinate on the region chunk.
- * @param y The y-coordinate on the region chunk.
- * @param objectId The object id.
- */
- public int getIndex(int x, int y, int objectId) {
- for (int i = 0; i < objects.length; i++) {
- Scenery o = get(x, y, i);
- if (o != null && ((objectId > -1 && o.getId() == objectId) || (objectId == -1 && o.getDefinition().hasOptions(false)))) {
- return i;
- }
- }
- return 0;
- }
-
- /**
- * Gets a scenery.
- * @param x The chunk x-coordinate.
- * @param y The chunk y-coordinate.
- * @param index The index (0 = default).
- * @return The object.
- */
- public Scenery get(int x, int y, int index) {
- return objects[index][x][y];
- }
-
- @Override
- public Scenery[] getObjects(int chunkX, int chunkY) {
- Scenery[] objects = new Scenery[ARRAY_SIZE];
- for (int i = 0; i < ARRAY_SIZE; i++) {
- objects[i] = this.objects[i][chunkX][chunkY];
- }
- return objects;
- }
-
- /**
- * Gets the objects.
- * @param index The index.
- * @return The objects array.
- */
- public Scenery[][] getObjects(int index) {
- return objects[index];
- }
-
- @Override
- public void setCurrentBase(Location currentBase) {
- for (int i = 0; i < objects.length; i++) {
- for (int x = 0; x < objects[i].length; x++) {
- for (int y = 0; y < objects[i][x].length; y++) {
- if (objects[i][x][y] != null) {
- Location newLoc = currentBase.transform(x, y, 0);
- objects[i][x][y].setLocation(newLoc);
- }
- }
- }
- }
- super.setCurrentBase(currentBase);
- }
-}
diff --git a/Server/src/main/core/game/world/map/Location.kt b/Server/src/main/core/game/world/map/Location.kt
index a202e4a49..0d2d01bb9 100644
--- a/Server/src/main/core/game/world/map/Location.kt
+++ b/Server/src/main/core/game/world/map/Location.kt
@@ -101,6 +101,8 @@ class Location(var hash: LocationHash) : Node(null, null) {
// Derived variables. Please note the following isn't a 1-1 representation of what is officially used.
// Region - (Officially: Map Square) 64x64
+ /** The region. */
+ val region: Region get() = RegionManager.forId(regionId)
/** Unique index "key" (Officially: region_uid) for a 64x64 area. Made of x and y joined bitwise together. Used in xteas.json. */
val regionId: Int get() = x shr 6 shl 8 or (y shr 6) // Cuts off the end 6 bits of both x y and joins them together. E.g. 10392
/** The chunk x index for this position (x/8). For sets of 8x8 area. THIS IS NAMED WRONG, THIS IS A CHUNK(8) NOT REGION(64). */
@@ -116,7 +118,14 @@ class Location(var hash: LocationHash) : Node(null, null) {
fun isInRegion(region: Int): Boolean = regionId == region
// Chunk - (Officially: Zones) 8x8
+ /** The chunk. */
+ val chunk: RegionChunk get() = region.chunks[chunkX][chunkY][z]
+ /** The chunk base on the world map. */
val chunkBase: Location get() = create(regionX shl 3, regionY shl 3, z)
+ /** The ABSOLUTE chunk x index for this position. (Same as regionX, but named correctly.) */
+ val absChunkX: Int get() = x shr 3 // shr 3 is essentially divide by 8
+ /** The ABSOLUTE chunk y index for this position. (Same as regionY, but named correctly.) */
+ val absChunkY: Int get() = y shr 3 // shr 3 is essentially divide by 8
/** The chunk x index for this position RELATIVE to a region[localX](x/8). THIS IS NOT GLOBAL CHUNK X. */
val chunkX: Int get() = localX shr 3 // shr 3 is essentially divide by 8 (note this is localX)
/** The chunk y index for this position RELATIVE to a region[localY](y/8). THIS IS NOT GLOBAL CHUNK Y. */
@@ -136,8 +145,6 @@ class Location(var hash: LocationHash) : Node(null, null) {
/** @return The local scene y-coordinate relative to another location's regionY. */
fun getSceneY(loc: Location): Int = y - (loc.regionY - 6 shl 3)
-
-
/** @return The location incremented by another location's coordinates. (POTENTIAL Z PROBLEMS HERE) */
// TODO: THE FOLLOWING TRANSFORM HAS SHIT Z HANDLING.
fun transform(l: Location): Location = Location(x + l.x, y + l.y, (z + l.z) % 4)
@@ -148,7 +155,6 @@ class Location(var hash: LocationHash) : Node(null, null) {
/** Location transformed by a Vector. Limit usage to more complicated functionality. */
fun transform(vector: Vector): Location = create(x + floor(vector.x).toInt(), y + floor(vector.y).toInt())
-
/** @return True if this location is 1 tile N, W, S, or E of the node. THIS CAN BE REFACTORED OUT. */
fun isNextTo(node: Node): Boolean {
val l = node.location
@@ -167,7 +173,7 @@ class Location(var hash: LocationHash) : Node(null, null) {
/** @return The straight-line(Euclidean) distance between this and another location. */
fun getDistance(other: Location): Double = getDistance(this, other)
- /** @returns ArrayList of the 8 cardinal and diagonal tiles. Order is important and follows [Direction] indexing **/
+ /** @returns ArrayList of the 8 cardinal and diagonal tiles. Order is important and follows [Direction] indexing */
val surroundingTiles: ArrayList get() {
val locations = ArrayList()
locations.add(transform(-1, -1, 0))
@@ -181,7 +187,7 @@ class Location(var hash: LocationHash) : Node(null, null) {
return locations
}
- /** @returns ArrayList of the 4 cardinal direction tiles. Order is important and follows [Direction] indexing **/
+ /** @returns ArrayList of the 4 cardinal direction tiles. Order is important and follows [Direction] indexing */
val cardinalTiles: ArrayList get() {
val locations = ArrayList()
locations.add(transform(-1, 0, 0))
@@ -191,7 +197,6 @@ class Location(var hash: LocationHash) : Node(null, null) {
return locations
}
-
/**
* Gets the directional components of a movement step.
* TODO: would prefer this in the other place. This doesn't exactly belong here since this isn't a common function for other instances of Location
diff --git a/Server/src/main/core/game/world/map/Region.java b/Server/src/main/core/game/world/map/Region.java
index ff87d8e6b..0a0ca85e4 100644
--- a/Server/src/main/core/game/world/map/Region.java
+++ b/Server/src/main/core/game/world/map/Region.java
@@ -1,9 +1,12 @@
package core.game.world.map;
import core.cache.Cache;
+import core.game.node.Node;
import core.game.node.entity.npc.NPC;
import core.game.node.entity.player.Player;
import core.game.node.entity.player.link.music.MusicZone;
+import core.game.node.scenery.Scenery;
+import core.game.node.scenery.SceneryBuilder;
import core.game.system.communication.CommunicationInfo;
import core.game.system.task.Pulse;
import core.game.world.map.build.DynamicRegion;
@@ -29,12 +32,21 @@ import static core.api.ContentAPIKt.log;
* @author Emperor
*/
public class Region {
-
/**
- * The default size of a region.
+ * The number of tiles per region.
*/
public static final int SIZE = 64;
+ /**
+ * The number of chunks per region.
+ */
+ public static final int CHUNKS_SIZE = 8;
+
+ /**
+ * The number of z levels per x,y coordinate.
+ */
+ public static final int PLANES = 4;
+
/**
* The region x-coordinate.
*/
@@ -46,9 +58,9 @@ public class Region {
private final int y;
/**
- * The region planes.
+ * The chunks in this region.
*/
- private final RegionPlane[] planes = new RegionPlane[4];
+ protected RegionChunk[][][] chunks = new RegionChunk[CHUNKS_SIZE][CHUNKS_SIZE][PLANES];
/**
* The activity pulse.
@@ -73,7 +85,7 @@ public class Region {
/**
* Keeps track of players and time in region for tolerance purposes
*/
- private final HashMap tolerances = new HashMap<>();
+ private final HashMap tolerances = new HashMap();
/**
* If the region is active.
@@ -106,9 +118,10 @@ public class Region {
private boolean build;
/**
- * If all planes should be updated when in this region (instead of just current one).
+ * Any scenery overrides for this region
*/
- private boolean updateAllPlanes;
+ private final ArrayList addSceneries = new ArrayList<>();
+ private final ArrayList removeSceneries = new ArrayList<>();
/**
* Constructs a new {@code Region} {@code Object}.
@@ -118,8 +131,14 @@ public class Region {
public Region(int x, int y) {
this.x = x;
this.y = y;
- for (int plane = 0; plane < 4; plane++) {
- planes[plane] = new RegionPlane(this, plane);
+ Location swCorner = getBaseLocation();
+ for (x = 0; x < CHUNKS_SIZE; x++) {
+ for (y = 0; y < CHUNKS_SIZE; y++) {
+ for (int z = 0; z < PLANES; z++) {
+ Location loc = swCorner.transform(x*RegionChunk.SIZE, y*RegionChunk.SIZE, z);
+ chunks[x][y][z] = new RegionChunk(loc, 0);
+ }
+ }
}
this.activityPulse = new Pulse(50) {
@Override
@@ -139,79 +158,57 @@ public class Region {
return Location.create(x << 6, y << 6, 0);
}
+ /**
+ * Gets the chunks.
+ * @return The chunks.
+ */
+ public RegionChunk[][][] getChunks() {
+ return chunks;
+ }
+
/**
* Adds a region zone to this region.
* @param zone The region zone.
*/
public void add(RegionZone zone) {
regionZones.add(zone);
- for (RegionPlane plane : planes) {
- for (NPC npc : plane.getNpcs()) {
- npc.getZoneMonitor().updateLocation(npc.getLocation());
- }
- for (Player p : plane.getPlayers()) {
- p.getZoneMonitor().updateLocation(p.getLocation());
+ for (int x = 0; x < CHUNKS_SIZE; x++) {
+ for (int y = 0; y < CHUNKS_SIZE; y++) {
+ for (int z = 0; z < PLANES; z++) {
+ for (NPC npc : chunks[x][y][z].getNpcs()) {
+ npc.getZoneMonitor().updateLocation(npc.getLocation());
+ }
+ for (Player player : chunks[x][y][z].getPlayers()) {
+ if (player != null) {
+ player.getZoneMonitor().updateLocation(player.getLocation());
+ }
+ }
+ }
}
}
}
public void remove(RegionZone zone) {
regionZones.remove(zone);
- for (RegionPlane plane : planes) {
- for (NPC npc : plane.getNpcs()) {
- npc.getZoneMonitor().updateLocation(npc.getLocation());
- }
- for (Player p : plane.getPlayers()) {
- p.getZoneMonitor().updateLocation(p.getLocation());
+ for (int x = 0; x < CHUNKS_SIZE; x++) {
+ for (int y = 0; y < CHUNKS_SIZE; y++) {
+ for (int z = 0; z < PLANES; z++) {
+ for (NPC npc : chunks[x][y][z].getNpcs()) {
+ npc.getZoneMonitor().updateLocation(npc.getLocation());
+ }
+ for (Player player : chunks[x][y][z].getPlayers()) {
+ player.getZoneMonitor().updateLocation(player.getLocation());
+ }
+ }
}
}
}
- /**
- * Adds a player to this region.
- * @param player The player.
- */
- public void add(Player player) {
- planes[player.getLocation().getZ()].add(player);
- tolerances.put(player.getUsername(), System.currentTimeMillis());
- flagActive();
- }
-
- /**
- * Adds an npc to this region.
- * @param npc The npc.
- */
- public void add(NPC npc) {
- planes[npc.getLocation().getZ()].add(npc);
- }
-
- /**
- * Removes an NPC from this region.
- * @param npc The NPC.
- */
- public void remove(NPC npc) {
- RegionPlane plane = npc.getViewport().getCurrentPlane();
- if (plane != null && plane != planes[npc.getLocation().getZ()]) {
- plane.remove(npc);
- }
- planes[npc.getLocation().getZ()].remove(npc);
- }
-
- /**
- * Removes a player from this region.
- * @param player The player.
- */
- public void remove(Player player) {
- player.getViewport().getCurrentPlane().remove(player);
- tolerances.remove(player.getUsername());
- checkInactive();
- }
-
/**
* Checks if player is tolerated by enemies in this region
*/
- public boolean isTolerated(Player player){
- return System.currentTimeMillis() - tolerances.getOrDefault(player.getUsername(), System.currentTimeMillis()) > TimeUnit.MINUTES.toMillis(10);
+ public boolean isTolerated(Player player) {
+ return System.currentTimeMillis() - tolerances.getOrDefault(player.getName(), System.currentTimeMillis()) > TimeUnit.MINUTES.toMillis(10);
}
/**
@@ -231,9 +228,13 @@ public class Region {
if (isViewed()) {
return false;
}
- for (RegionPlane p : planes) {
- if (!p.getPlayers().isEmpty()) {
- return false;
+ for (int x = 0; x < CHUNKS_SIZE; x++) {
+ for (int y = 0; y < CHUNKS_SIZE; y++) {
+ for (int z = 0; z < PLANES; z++) {
+ if (!chunks[x][y][z].getPlayers().isEmpty()) {
+ return false;
+ }
+ }
}
}
if (runPulse) {
@@ -246,14 +247,6 @@ public class Region {
return true;
}
- /**
- * Checks if this region has the inactivity flagging pulse running.
- * @return {@code True} if so.
- */
- public boolean isPendingRemoval() {
- return activityPulse.isRunning();
- }
-
/**
* Flags the region as active.
*/
@@ -262,10 +255,14 @@ public class Region {
if (!active) {
active = true;
load(this);
- for (RegionPlane r : planes) {
- for (NPC n : r.getNpcs()) {
- if (n.isActive()) {
- Repository.addRenderableNPC(n);
+ for (int x = 0; x < CHUNKS_SIZE; x++) {
+ for (int y = 0; y < CHUNKS_SIZE; y++) {
+ for (int z = 0; z < PLANES; z++) {
+ for (NPC npc : chunks[x][y][z].getNpcs()) {
+ if (npc.isActive()) {
+ Repository.addRenderableNPC(npc);
+ }
+ }
}
}
}
@@ -285,7 +282,7 @@ public class Region {
* Flags the region as inactive.
*/
public boolean flagInactive() {
- return flagInactive(false);
+ return flagInactive(false);
}
/**
@@ -299,7 +296,7 @@ public class Region {
/**
* Loads the flags for a region.
* @param r The region.
- * @param build if all objects in this region should be stored (rather than just the ones with options).
+ * @param build If the region can be edited.
*/
public static void load(Region r, boolean build) {
try {
@@ -318,11 +315,13 @@ public class Region {
return;
}
- byte[][][] mapscapeData = new byte[4][SIZE][SIZE];
- for (RegionPlane plane : r.planes) {
- plane.getFlags().setLandscape(new boolean[SIZE][SIZE]);
- //plane.getFlags().setClippingFlags(new int[SIZE][SIZE]);
- //plane.getProjectileFlags().setClippingFlags(new int[SIZE][SIZE]);
+ byte[][][] mapscapeData = new byte[PLANES][SIZE][SIZE];
+ for (int x = 0; x < CHUNKS_SIZE; x++) {
+ for (int y = 0; y < CHUNKS_SIZE; y++) {
+ for (int z = 0; z < PLANES; z++) {
+ r.chunks[x][y][z].getFlags().setLandscape(new boolean[RegionChunk.SIZE][RegionChunk.SIZE]);
+ }
+ }
}
if (mapscapeId > -1) {
ByteBuffer mapscape = ByteBuffer.wrap(Cache.getIndexes()[5].getCacheFile().getContainerUnpackedData(mapscapeId));
@@ -338,50 +337,63 @@ public class Region {
}
r.hasFlags = true;
try {
- LandscapeParser.parse(r, mapscapeData, ByteBuffer.wrap(landscape), build);
+ LandscapeParser.parse(r, r.chunks, mapscapeData, ByteBuffer.wrap(landscape), build);
} catch (Throwable t) {
new Throwable("Failed parsing region " + regionId + "!", t).printStackTrace();
}
}
- MapscapeParser.clipMapscape(r, mapscapeData);
+ MapscapeParser.clipMapscape(r, r.chunks, mapscapeData);
+ for (Scenery object : r.removeSceneries) {
+ // Get the actual object, not the instance that's kept in the removeScenery array
+ Scenery realObject = RegionManager.getObject(object.getLocation()); //can be extended to take into account type and rotation if needed
+ if (realObject != null) {
+ SceneryBuilder.remove(realObject);
+ }
+ }
+ for (Scenery object : r.addSceneries) {
+ SceneryBuilder.add(object);
+ }
} catch (Throwable e) {
e.printStackTrace();
}
}
- public static boolean unload(Region r) {
- return unload(r, false);
- }
-
/**
* Unloads a region.
* @param r The region.
*/
- public static boolean unload(Region r, boolean force) {
+ public boolean unload(Region r, boolean force) {
if (!force && r.isViewed()) {
log(CommunicationInfo.class, Log.ERR, "Players viewing region!");
r.flagActive();
return false;
}
- for (RegionPlane p : r.planes) {
- if (!force && !p.getPlayers().isEmpty()) {
- log(CommunicationInfo.class, Log.ERR, "Players still in region!");
- r.flagActive();
- return false;
- }
- }
- for (RegionPlane p : r.planes) {
- p.clear();
- if (!(r instanceof DynamicRegion)) {
- for (NPC n : p.getNpcs()) {
- n.onRegionInactivity();
+ for (int x = 0; x < CHUNKS_SIZE; x++) {
+ for (int y = 0; y < CHUNKS_SIZE; y++) {
+ for (int z = 0; z < PLANES; z++) {
+ if (!force && !chunks[x][y][z].getPlayers().isEmpty()) {
+ log(CommunicationInfo.class, Log.ERR, "Players still in region! (region id " + getId() + "; " + chunks[x][y][z].getPlayers().size() + " players on a chunk)");
+ r.flagActive();
+ return false;
+ }
}
}
}
- if (r.isBuild())
- r.setLoaded(false);
+ for (int x = 0; x < CHUNKS_SIZE; x++) {
+ for (int y = 0; y < CHUNKS_SIZE; y++) {
+ for (int z = 0; z < PLANES; z++) {
+ if (!(r instanceof DynamicRegion)) {
+ for (NPC npc : chunks[x][y][z].getNpcs()) {
+ npc.onRegionInactivity();
+ }
+ }
+ chunks[x][y][z].clear();
+ }
+ }
+ }
+ r.setLoaded(false);
r.activityPulse.stop();
- return true;
+ return true;
}
/**
@@ -426,17 +438,6 @@ public class Region {
return active;
}
- /**
- * Sets the active.
- * @param active The active to set.
- * @deprecated This should not be used, instead use the {@link #flagInactive()},
- * {@link #flagActive()} & {@link #checkInactive()} methods to safely change the activity state.
- */
- @Deprecated
- public void setActive(boolean active) {
- this.active = active;
- }
-
/**
* Gets the region id.
* @return The region id.
@@ -469,14 +470,6 @@ public class Region {
return y;
}
- /**
- * Gets the planes.
- * @return The planes.
- */
- public RegionPlane[] getPlanes() {
- return planes;
- }
-
/**
* Sets the region-wide music track.
*/
@@ -532,14 +525,6 @@ public class Region {
return hasFlags;
}
- /**
- * Sets the hasFlags.
- * @param hasFlags The hasFlags to set.
- */
- public void setHasFlags(boolean hasFlags) {
- this.hasFlags = hasFlags;
- }
-
/**
* Sets the region time out duration.
* @param ticks The amount of ticks before the region is flagged as inactive.
@@ -564,14 +549,6 @@ public class Region {
this.loaded = loaded;
}
- /**
- * Sets the viewAmount.
- * @param viewAmount The viewAmount to set.
- */
- public void setViewAmount(int viewAmount) {
- this.viewAmount = viewAmount;
- }
-
/**
* Gets the build.
* @return the build
@@ -588,19 +565,69 @@ public class Region {
this.build = build;
}
- /**
- * Gets the updateAllPlanes value.
- * @return The updateAllPlanes.
- */
- public boolean isUpdateAllPlanes() {
- return updateAllPlanes;
+ public List assembleObjectList(int z) {
+ ArrayList list = new ArrayList<>();
+ for (int x = 0; x < CHUNKS_SIZE; x++) {
+ for (int y = 0; y < CHUNKS_SIZE; y++) {
+ RegionChunk chunk = chunks[x][y][z];
+ for (int offsetX = 0; offsetX < RegionChunk.SIZE; offsetX++) {
+ for (int offsetY = 0; offsetY < RegionChunk.SIZE; offsetY++) {
+ for (int i = 0; i < RegionChunk.ARRAY_SIZE; i++) {
+ Scenery object = chunk.getObjects()[offsetX][offsetY][i];
+ if (object != null) {
+ list.add(object);
+ }
+ }
+ }
+ }
+ }
+ }
+ return list;
+ }
+
+ public List assembleNpcList(int z) {
+ ArrayList list = new ArrayList<>();
+ for (int x = 0; x < CHUNKS_SIZE; x++) {
+ for (int y = 0; y < CHUNKS_SIZE; y++) {
+ RegionChunk chunk = chunks[x][y][z];
+ list.addAll(chunk.getNpcs());
+ }
+ }
+ return list;
+ }
+
+ public List assemblePlayerList(int z) {
+ ArrayList list = new ArrayList<>();
+ for (int x = 0; x < CHUNKS_SIZE; x++) {
+ for (int y = 0; y < CHUNKS_SIZE; y++) {
+ RegionChunk chunk = chunks[x][y][z];
+ list.addAll(chunk.getPlayers());
+ }
+ }
+ return list;
+ }
+
+ public List assembleNodeList(int z) {
+ ArrayList list = new ArrayList<>(assembleObjectList(z));
+ list.addAll(assemblePlayerList(z));
+ list.addAll(assembleNpcList(z));
+ return list;
}
/**
- * Sets the updateAllPlanes value.
- * @param updateAllPlanes The updateAllPlanes to set.
+ * Getter for region aggro tolerances.
*/
- public void setUpdateAllPlanes(boolean updateAllPlanes) {
- this.updateAllPlanes = updateAllPlanes;
+ public HashMap getTolerances() {
+ return tolerances;
+ }
+
+ /**
+ * Getters for addScenery and removeScenery.
+ */
+ public ArrayList getAddSceneries() {
+ return addSceneries;
+ }
+ public ArrayList getRemoveSceneries() {
+ return removeSceneries;
}
}
diff --git a/Server/src/main/core/game/world/map/RegionChunk.java b/Server/src/main/core/game/world/map/RegionChunk.java
index 7b3fb71c8..a252921f0 100644
--- a/Server/src/main/core/game/world/map/RegionChunk.java
+++ b/Server/src/main/core/game/world/map/RegionChunk.java
@@ -1,19 +1,25 @@
package core.game.world.map;
+import core.game.node.Node;
+import core.game.node.entity.npc.NPC;
import core.game.node.entity.player.Player;
import core.game.node.item.GroundItem;
import core.game.node.item.Item;
+import core.game.node.scenery.Constructed;
import core.game.node.scenery.Scenery;
+import core.game.node.scenery.SceneryBuilder;
+import core.game.world.map.build.ChunkFlags;
+import core.game.world.update.ChunkDirtyListener;
+import core.game.world.update.flag.chunk.ItemUpdateFlag;
+import core.net.packet.PacketRepository;
+import core.net.packet.context.BuildItemContext;
+import core.net.packet.out.*;
import core.tools.Log;
-import core.tools.SystemLogger;
import core.game.world.map.build.DynamicRegion;
import core.game.world.map.build.LandscapeParser;
import core.game.world.update.flag.UpdateFlag;
import core.net.packet.IoBuffer;
-import core.net.packet.out.ClearScenery;
-import core.net.packet.out.ConstructGroundItem;
-import core.net.packet.out.ConstructScenery;
-import core.net.packet.out.UpdateAreaPosition;
+import org.rs09.consts.Items;
import java.util.ArrayList;
import java.util.List;
@@ -22,16 +28,26 @@ import static core.api.ContentAPIKt.log;
/**
* Represents a region chunk.
- * @author Emperor
+ * @author Emperor, Player Name
*
*/
public class RegionChunk {
+ /**
+ * The maximum amount of objects to be stored on one tile in the chunk.
+ * Four objects per tile is the authentic RuneTek engine limit.
+ */
+ public static final int ARRAY_SIZE = 4;
/**
* The chunk size.
*/
public static final int SIZE = 8;
+ /**
+ * Notified whenever a chunk is flagged, so the rendering pipeline can reset only the chunks that were actually touched.
+ */
+ public static ChunkDirtyListener dirtyListener = chunk -> {};
+
/**
* The base location of the copied region chunk.
*/
@@ -42,20 +58,27 @@ public class RegionChunk {
*/
protected Location currentBase;
- /**
- * The region plane.
- */
- protected RegionPlane plane;
-
/**
* The items in this chunk.
*/
- protected List items;
+ protected List items = new ArrayList<>();
/**
- * The scenerys in this chunk.
+ * The static objects in this chunk. Only used as a reference, do not act on these unless you are the landscape-
+ * parsing code.
+ * The game protocol requires us to send separate updates for dynamically added scenery and dynamically
+ * removed scenery. We implement this by maintaining two lists: one of static scenery, which contains the
+ * reference state obtained from the cache, and one of dynamic scenery, which is the actual current state of
+ * the sceneries on a chunk. This means that during landscape-parsing (and, crucially, only then), we need
+ * to add objects to both the static and dynamic lists. All object interactions in the game, however, only
+ * use the dynamic list (hence why that is just called 'objects').
*/
- protected Scenery[][] objects;
+ protected Scenery[][][] statObjects;
+
+ /**
+ * The (dynamic, actual) objects in this chunk.
+ */
+ protected Scenery[][][] objects;
/**
* The rotation.
@@ -65,28 +88,253 @@ public class RegionChunk {
/**
* The update flags.
*/
- private List> flags = new ArrayList<>(20);
+ private final List> updateFlags = new ArrayList<>(20);
+
+ /**
+ * The region flags.
+ */
+ private ChunkFlags flags;
+
+ /**
+ * The region projectile flags.
+ */
+ private ChunkFlags projectileFlags;
+
+ /**
+ * The players on this chunk.
+ */
+ private final List players = new ArrayList<>();
+
+ /**
+ * The NPCs on this chunk.
+ */
+ private final List npcs = new ArrayList<>();
/**
* Constructs a new {@code RegionChunk} {@code Object}.
* @param base The base location of the region chunk.
* @param rotation The rotation.
*/
- public RegionChunk(Location base, int rotation, RegionPlane plane) {
+ public RegionChunk(Location base, int rotation) {
this.base = base;
this.currentBase = base;
this.rotation = rotation;
- this.plane = plane;
- this.objects = new Scenery[SIZE][SIZE];
+ this.statObjects = new Scenery[SIZE][SIZE][ARRAY_SIZE];
+ this.objects = new Scenery[SIZE][SIZE][ARRAY_SIZE];
+ this.flags = new ChunkFlags(base.getX(), base.getY(), base.getZ());
+ this.projectileFlags = new ChunkFlags(base.getX(), base.getY(), base.getZ(), true);
+ }
+
+ /**
+ * Corrects objects' Locations when copying them from a template region into an instance - public version.
+ */
+ public void rebaseObjects() {
+ rebaseObjects(statObjects);
+ rebaseObjects(objects);
+ }
+
+ /**
+ * Corrects objects' Locations when copying them from a template region into an instance - private version.
+ */
+ private void rebaseObjects(Scenery[][][] objects) {
+ for (int i = 0; i < ARRAY_SIZE; i++) {
+ for (int x = 0; x < SIZE; x++) {
+ for (int y = 0; y < SIZE; y++) {
+ Scenery o = objects[x][y][i];
+ if (o == null) {
+ continue;
+ }
+ o.setLocation(getCurrentBase().transform(x, y, 0));
+ }
+ }
+ }
+ }
+
+ /**
+ * Makes a deep copy of an object list - private version.
+ */
+ private void copyObjects(Scenery[][][] src, Scenery[][][] dest) {
+ for (int x = 0; x < SIZE; x++) {
+ for (int y = 0; y < SIZE; y++) {
+ for (int i = 0; i < ARRAY_SIZE; i++) {
+ Scenery o = src[x][y][i];
+ if (o == null) {
+ continue;
+ }
+ Scenery copy = o.transform(o.getId());
+ if (o instanceof Constructed) {
+ dest[x][y][i] = copy.asConstructed();
+ } else {
+ dest[x][y][i] = copy;
+ }
+ dest[x][y][i].setActive(o.isActive());
+ dest[x][y][i].setRenderable(o.isRenderable());
+ }
+ }
+ }
}
/**
* Copies the region chunk.
- * @param plane The region plane.
- * @return The region chunk.
+ * @return The destination chunk.
*/
- public BuildRegionChunk copy(RegionPlane plane) {
- return new BuildRegionChunk(base, rotation, plane, this.objects);
+ public RegionChunk copy() {
+ RegionChunk chunk = new RegionChunk(base, rotation);
+ copyObjects(statObjects, chunk.statObjects); //note that the objects' locations have not been repointed to the new chunk yet, since we don't know _where_ the new chunk will be at this point
+ copyObjects(objects, chunk.objects);
+ return chunk;
+ }
+
+ /**
+ * Adds the scenery to the static and dynamic object lists. You never want this unless you are the landscape-parsing
+ * code.
+ * @param object The object to add.
+ */
+ public void addStatDyn(Scenery object, int chunkOffsetX, int chunkOffsetY) {
+ for (int i = 0; i < ARRAY_SIZE; i++) {
+ Scenery current = statObjects[chunkOffsetX][chunkOffsetY][i];
+ if (current == null) {
+ statObjects[chunkOffsetX][chunkOffsetY][i] = object;
+ objects[chunkOffsetX][chunkOffsetY][i] = object.transform(object.getId()); //deep copy so that active/renderable flags don't synchronize across multiple copies of e.g. a POH
+ return;
+ }
+ if (current.getId() == object.getId()) {
+ // It's possible that we already have this object. One instance of this was found at 1906, 5082, 0,
+ // where our cache has more than one copy of the same [Scenery 15349, [1906, 5082, 0], type=22, rot=1]
+ // object. Since authentically, the engine can only support one of the same type of object per tile,
+ // if the ID matches we can be confident that this is a duplicate.
+ return;
+ }
+ }
+ throw new IllegalStateException("RC addStatDyn insufficient array length for storing object " + object);
+ }
+
+ /**
+ * Adds the scenery to the dynamic object list.
+ * @param object The object to add.
+ * @return The slot it was added into.
+ */
+ public int add(Scenery object) {
+ int chunkOffsetX = object.getLocation().getChunkOffsetX();
+ int chunkOffsetY = object.getLocation().getChunkOffsetY();
+ for (int i = 0; i < ARRAY_SIZE; i++) {
+ Scenery current = objects[chunkOffsetX][chunkOffsetY][i];
+ if (current == null) {
+ objects[chunkOffsetX][chunkOffsetY][i] = object.asConstructed();
+ object.setRenderable(true);
+ object.setActive(true);
+ return i;
+ }
+ if (current.equals(object)) {
+ // Just reactivate the old instance
+ current.setRenderable(true);
+ current.setActive(true);
+ return i;
+ }
+ }
+ throw new IllegalStateException("RC add insufficient array length for storing object " + object);
+ }
+
+ /**
+ * Adds an NPC to this chunk.
+ * @param npc The NPC to add.
+ */
+ public void add(NPC npc) {
+ npcs.add(npc);
+ }
+
+ /**
+ * Adds a player to this chunk.
+ * @param player The player.
+ */
+ public void addPlayer(Player player) {
+ players.add(player);
+ currentBase.getRegion().flagActive();
+ }
+
+ /**
+ * Adds an item to this region.
+ * @param item The item.
+ */
+ public void add(GroundItem item) {
+ getItems().add(item);
+ if (item.isPrivate()) {
+ if (item.getDropper() != null) {
+ PacketRepository.send(ConstructGroundItem.class, new BuildItemContext(item.getDropper(), item));
+ }
+ return;
+ }
+ flag(new ItemUpdateFlag(item, ItemUpdateFlag.CONSTRUCT_TYPE));
+ }
+
+ /**
+ * Removes an NPC from this chunk.
+ * @param npc The NPC.
+ */
+ public void remove(NPC npc) {
+ npcs.remove(npc);
+ }
+
+ /**
+ * Removes a player from this chunk.
+ * @param player The player.
+ */
+ public void removePlayer(Player player) {
+ players.remove(player);
+ currentBase.getRegion().checkInactive();
+ }
+
+ /**
+ * Removes an item from this chunk.
+ * @param item The ground item.
+ */
+ public void remove(GroundItem item) {
+ Location l = item.getLocation();
+ if (!items.remove(item)) {
+ return;
+ }
+ if (item.isPrivate()) {
+ if (item.getDropper() != null && item.getDropper().isPlaying() && item.getDropper().getLocation().withinDistance(l)) {
+ PacketRepository.send(ClearGroundItem.class, new BuildItemContext(item.getDropper(), item));
+ }
+ return;
+ }
+ flag(new ItemUpdateFlag(item, ItemUpdateFlag.REMOVE_TYPE));
+ }
+
+ /**
+ * Gets the npcs.
+ * @return The npcs.
+ */
+ public List getNpcs() {
+ return npcs;
+ }
+
+ /**
+ * Assembles a flat object list.
+ * @return The objects.
+ */
+ public List assembleObjectList() {
+ ArrayList list = new ArrayList<>(ARRAY_SIZE);
+ for (int offsetX = 0; offsetX < SIZE; offsetX++) {
+ for (int offsetY = 0; offsetY < SIZE; offsetY++) {
+ for (int i = 0; i < ARRAY_SIZE; i++) {
+ Scenery object = objects[offsetX][offsetY][i];
+ if (object != null) {
+ list.add(object);
+ }
+ }
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Gets the players.
+ * @return The players.
+ */
+ public List getPlayers() {
+ return players;
}
/**
@@ -94,18 +342,27 @@ public class RegionChunk {
* @param flag The flag.
*/
public void flag(UpdateFlag> flag) {
- flags.add(flag);
+ updateFlags.add(flag);
+ dirtyListener.onFlagged(this);
}
/**
* Clears the region chunk.
*/
public void clear() {
- flags.clear();
- if (items != null && plane.getRegion() instanceof DynamicRegion) {
+ updateFlags.clear();
+ Region region = RegionManager.forId(currentBase.getRegionId());
+ if (items != null && region instanceof DynamicRegion) {
items.clear();
items = null;
}
+ for (int x = 0; x < SIZE; x++) {
+ for (int y = 0; y < SIZE; y++) {
+ for (int i = 0; i < ARRAY_SIZE; i++) {
+ statObjects[x][y][i] = objects[x][y][i] = null;
+ }
+ }
+ }
}
/**
@@ -123,29 +380,26 @@ public class RegionChunk {
* Writes the region chunk update data on the buffer.
* @param player The player we're updating for.
* @param buffer The buffer to write on.
- * @return {@code True} if an update occured.
+ * @return {@code True} if an update occurred.
*/
protected boolean appendUpdate(Player player, IoBuffer buffer) {
boolean updated = false;
- int baseX = currentBase.getLocalX();
- int baseY = currentBase.getLocalY();
for (int x = 0; x < SIZE; x++) {
for (int y = 0; y < SIZE; y++) {
- Scenery dyn = objects[x][y];
- if (dyn == null || plane.getObjects() == null) {
- continue;
- }
- Scenery stat = plane.getObjects()[baseX + x][baseY + y];
- if (!dyn.isRenderable() && stat != null) {
- ClearScenery.write(buffer, stat);
- updated = true;
- }
- else if (dyn != stat) {
- if (stat != null) {
- ClearScenery.write(buffer, stat);
+ for (int i = 0; i < ARRAY_SIZE; i++) {
+ Scenery dyn = objects[x][y][i];
+ Scenery stat = statObjects[x][y][i];
+ if (dyn == stat) {
+ continue;
+ }
+ if (stat != null && (dyn == null || !dyn.isRenderable())) {
+ ClearScenery.write(buffer, stat);
+ updated = true;
+ }
+ if (dyn != null && dyn.isRenderable()) {
+ ConstructScenery.write(buffer, dyn);
+ updated = true;
}
- ConstructScenery.write(buffer, dyn);
- updated = true;
}
}
}
@@ -161,9 +415,8 @@ public class RegionChunk {
return updated;
}
-
public ArrayList drawItems(List items, Player player) {
- ArrayList totalItems = items != null ? new ArrayList(items) : new ArrayList();
+ ArrayList totalItems = items != null ? new ArrayList<>(items) : new ArrayList();
if (player.getAttribute("chunkdraw", false)) {
Location l = currentBase;
@@ -175,7 +428,7 @@ public class RegionChunk {
else if (x == 0 || x == SIZE - 1)
add = true;
if (add)
- totalItems.add(new GroundItem(new Item(13444), l.transform(x, y, 0), player));
+ totalItems.add(new GroundItem(new Item(Items.ABYSSAL_WHIP_13444), l.transform(x, y, 0), player));
}
}
}
@@ -200,7 +453,7 @@ public class RegionChunk {
add = true;
if (add)
- totalItems.add(new GroundItem(new Item(13444), l.transform(x,y,0), player));
+ totalItems.add(new GroundItem(new Item(Items.ABYSSAL_WHIP_13444), l.transform(x,y,0), player));
}
}
@@ -225,7 +478,7 @@ public class RegionChunk {
public void update(Player player) {
if (isUpdated()) {
IoBuffer buffer = UpdateAreaPosition.getChunkUpdateBuffer(player, currentBase);
- Object[] flagsArray = flags.toArray();
+ Object[] flagsArray = updateFlags.toArray();
int size = flagsArray.length;
for (int i = 0; i < size; i++) {
UpdateFlag> flag = (UpdateFlag>) flagsArray[i];
@@ -235,86 +488,13 @@ public class RegionChunk {
}
}
- /**
- * Rotates the chunk.
- * @param direction The direction.
- */
- public void rotate(Direction direction) {
- if (rotation != 0) {
- log(this.getClass(), Log.ERR, "Region chunk was already rotated!");
- return;
- }
- Scenery[][] copy = new Scenery[SIZE][SIZE];
- Scenery[][] staticCopy = new Scenery[SIZE][SIZE];
- int baseX = currentBase.getLocalX();
- int baseY = currentBase.getLocalY();
- for (int x = 0; x < SIZE; x++) {
- for (int y = 0; y < SIZE; y++) {
- copy[x][y] = objects[x][y];
- staticCopy[x][y] = plane.getObjects()[baseX + x][baseY + y];
- objects[x][y] = plane.getObjects()[baseX + x][baseY + y] = null;
- plane.getFlags().clearFlag(baseX + x, baseY + y);
- }
- }
- rotation = direction.toInteger();
- for (int x = 0; x < SIZE; x++) {
- for (int y = 0; y < SIZE; y++) {
- Scenery object = copy[x][y];
- Scenery stat = staticCopy[x][y];
- boolean match = object == stat;
- if (stat == null) {
- continue;
- }
- int[] pos = getRotatedPosition(x, y, stat.getDefinition().getSizeX(), stat.getDefinition().getSizeY(), stat.getRotation(), rotation);
- if (object != null) {
- object = object.transform(object.getId(), (object.getRotation() + rotation) % 4, object.getLocation().transform(pos[0] - x, pos[1] - y, 0));
- LandscapeParser.flagScenery(plane, baseX + pos[0], baseY + pos[1], object, true, true);
- }
- if (match) {
- stat = object;
- } else {
- stat = stat.transform(stat.getId(), (stat.getRotation() + rotation) % 4, stat.getLocation().transform(pos[0] - x, pos[1] - y, 0));
- }
- plane.getObjects()[baseX + pos[0]][baseY + pos[1]] = stat;
- }
- }
- }
-
- /**
- * Gets the new coordinates for an object/chunk tile when rotating.
- * @param x The current x-coordinate.
- * @param y The current y-coordinate.
- * @param sizeX The x-size of the object.
- * @param sizeY The y-size of the object.
- * @param rotation The object rotation.
- * @param chunkRotation The chunk rotation.
- * @return The new x-coordinate.
- */
- public static int[] getRotatedPosition(int x, int y, int sizeX, int sizeY, int rotation, int chunkRotation) {
- if ((rotation & 0x1) == 1) {
- int s = sizeX;
- sizeX = sizeY;
- sizeY = s;
- }
- if (chunkRotation == 0) {
- return new int[] { x, y };
- }
- if (chunkRotation == 1) {
- return new int[] { y, 7 - x - (sizeX - 1) };
- }
- if (chunkRotation == 2) {
- return new int[] { 7 - x - (sizeX - 1), 7 - y - (sizeY - 1) };
- }
- return new int[] { 7 - y - (sizeY - 1), x };
- }
-
/**
* Gets the items.
* @return The items.
*/
public List getItems() {
if (items == null) {
- items = new ArrayList();
+ items = new ArrayList<>();
}
return items;
}
@@ -328,29 +508,65 @@ public class RegionChunk {
}
/**
- * Gets the scenerys located on the coordinates in this chunk.
- * @param chunkX The chunk x-coordinate (0-7).
- * @param chunkY The chunk y-coordinate (0-7).
+ * Gets the dynamic sceneries located on the coordinates in this chunk.
+ * @param chunkOffsetX The x coordinate within the chunk (0-7).
+ * @param chunkOffsetY The y coordinate within the chunk (0-7).
* @return The objects.
*/
- public Scenery[] getObjects(int chunkX, int chunkY) {
- return new Scenery[] { objects[chunkX][chunkY] };
+ public Scenery[] getObjects(int chunkOffsetX, int chunkOffsetY) {
+ return objects[chunkOffsetX][chunkOffsetY];
+ }
+
+ /**
+ * Gets the static sceneries located on the coordinates in this chunk.
+ * @param chunkOffsetX The x coordinate within the chunk (0-7).
+ * @param chunkOffsetY The y coordinate within the chunk (0-7).
+ * @return The objects.
+ */
+ public Scenery[] getStatObjects(int chunkOffsetX, int chunkOffsetY) {
+ return statObjects[chunkOffsetX][chunkOffsetY];
+ }
+
+ /**
+ * Gets the static objects.
+ * @return The static objects.
+ */
+ public Scenery[][][] getStatObjects() {
+ return statObjects;
}
/**
* Gets the objects.
* @return The objects.
*/
- public Scenery[][] getObjects() {
+ public Scenery[][][] getObjects() {
return objects;
}
/**
- * Sets the objects.
- * @param objects The objects to set.
+ * Gets the objects index for the given object id and/or scenery type.
+ * @param x The x-coordinate on the region chunk.
+ * @param y The y-coordinate on the region chunk.
+ * @param objectId The object id. -1 if any.
+ * @param type The scenery type.
*/
- public void setObjects(Scenery[][] objects) {
- this.objects = objects;
+ public int getIndex(int x, int y, int objectId, int type) {
+ for (int i = 0; i < ARRAY_SIZE; i++) {
+ Scenery o = objects[x][y][i];
+ if (o != null) {
+ if (objectId < 0) {
+ if (type >= 0 && o.getType() != type) {
+ continue;
+ }
+ return i;
+ } else {
+ if (o.getId() == objectId) {
+ return i;
+ }
+ }
+ }
+ }
+ return -1;
}
/**
@@ -390,22 +606,14 @@ public class RegionChunk {
* @return The updated.
*/
public boolean isUpdated() {
- return !flags.isEmpty();
+ return !updateFlags.isEmpty();
}
/**
* Resets the flags.
*/
- public void resetFlags() {
- flags.clear();
- }
-
- /**
- * Gets the region plane.
- * @return The plane.
- */
- public RegionPlane getPlane() {
- return plane;
+ public void resetUpdateFlags() {
+ updateFlags.clear();
}
/**
@@ -424,18 +632,131 @@ public class RegionChunk {
this.currentBase = currentBase;
}
- public void rebuildFlags(RegionPlane from) {
- for (int x = 0; x < 8; x++) {
- for (int y = 0; y < 8; y++) {
- Location loc = currentBase.transform(x,y,0);
- Location fromLoc = base.transform(x,y,0);
- plane.getFlags().getLandscape()[loc.getLocalX()][loc.getLocalY()] = from.getFlags().getLandscape()[fromLoc.getLocalX()][fromLoc.getLocalY()];
- plane.getFlags().clearFlag(x, y);
- Scenery obj = objects[x][y];
- if (obj != null)
- LandscapeParser.flagScenery(plane, loc.getLocalX(), loc.getLocalY(), obj, false, true);
+ /**
+ * Reinitializes the clipping flags; used when a region is copied into a new dynamic region.
+ */
+ public void resetClippingFlags() {
+ flags = new ChunkFlags(currentBase.getX(), currentBase.getY(), currentBase.getZ());
+ projectileFlags = new ChunkFlags(currentBase.getX(), currentBase.getY(), currentBase.getZ(), true);
+ }
+
+ /**
+ * Rebuilds the clipping flags based on those of a source chunk (used when a chunk is being replaced).
+ * @param from The region plane to get the new clipping flags from.
+ */
+ public void rebuildClippingFlags(RegionChunk from) {
+ for (int x = 0; x < SIZE; x++) {
+ for (int y = 0; y < SIZE; y++) {
+ // Import the landscape flags from the template chunk to the new chunk
+ flags.getLandscape()[x][y] = from.getFlags().getLandscape()[x][y];
+ projectileFlags.getLandscape()[x][y] = from.getProjectileFlags().getLandscape()[x][y];
+ // Reflag any objects
+ for (int i = 0; i < ARRAY_SIZE; i++) {
+ Scenery obj = objects[x][y][i];
+ if (obj != null) {
+ LandscapeParser.flagScenery(this, x, y, obj, false, true);
+ }
+ }
}
}
}
+ /**
+ * Rotation consts. Greg loves these :)
+ */
+ private final static int NORTH_ROTATION = 0;
+ private final static int EAST_ROTATION = 1;
+ private final static int SOUTH_ROTATION = 2;
+ private final static int WEST_ROTATION = 3;
+ private final static int NUMBER_OF_CARDINAL_ROTATIONS = 4;
+
+ /**
+ * Rotates the chunk.
+ * @param direction The direction.
+ */
+ public void rotate(Direction direction) {
+ if (rotation != 0) {
+ log(this.getClass(), Log.ERR, "Region chunk was already rotated!");
+ return;
+ }
+ Scenery[][][] copy = new Scenery[SIZE][SIZE][ARRAY_SIZE];
+ for (int x = 0; x < SIZE; x++) {
+ for (int y = 0; y < SIZE; y++) {
+ for (int i = 0; i < ARRAY_SIZE; i++) {
+ Scenery object = copy[x][y][i] = objects[x][y][i];
+ if (object != null) {
+ SceneryBuilder.remove(object);
+ objects[x][y][i] = null;
+ }
+ }
+ }
+ }
+ clear();
+ switch (direction) {
+ case NORTH: rotation = NORTH_ROTATION; break;
+ case EAST: rotation = EAST_ROTATION; break;
+ case SOUTH: rotation = SOUTH_ROTATION; break;
+ case WEST: rotation = WEST_ROTATION; break;
+ default: rotation = (direction.toInteger() + (direction.toInteger() % 2 == 0 ? 2 : 0)) % NUMBER_OF_CARDINAL_ROTATIONS;
+ log(this.getClass(), Log.ERR, "Attempted to rotate a chunk in a non-cardinal direction - using fallback rotation code. This should be investigated!");
+ break;
+ };
+ for (int x = 0; x < SIZE; x++) {
+ for (int y = 0; y < SIZE; y++) {
+ for (int i = 0; i < ARRAY_SIZE; i++) {
+ Scenery object = copy[x][y][i];
+ if (object != null) {
+ int[] pos = getRotatedPosition(x, y, object.getDefinition().getSizeX(), object.getDefinition().getSizeY(), object.getRotation(), rotation);
+ Scenery obj = object.transform(object.getId(), (object.getRotation() + rotation) % NUMBER_OF_CARDINAL_ROTATIONS, object.getLocation().transform(pos[0] - x, pos[1] - y, 0));
+ if (object instanceof Constructed) {
+ obj = obj.asConstructed();
+ }
+ obj.setActive(object.isActive());
+ obj.setRenderable(object.isRenderable());
+ LandscapeParser.flagScenery(this, pos[0], pos[1], obj, true, true);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the new coordinates for an object/chunk tile when rotating.
+ * @param x The current x-coordinate.
+ * @param y The current y-coordinate.
+ * @param sizeX The x-size of the object.
+ * @param sizeY The y-size of the object.
+ * @param rotation The object rotation.
+ * @param chunkRotation The chunk rotation.
+ * @return The new x-coordinate.
+ */
+ public static int[] getRotatedPosition(int x, int y, int sizeX, int sizeY, int rotation, int chunkRotation) {
+ if ((rotation & 0x1) == 1) {
+ int s = sizeX;
+ sizeX = sizeY;
+ sizeY = s;
+ }
+ if (chunkRotation == 0) {
+ return new int[] { x, y };
+ }
+ if (chunkRotation == 1) {
+ return new int[] { y, (SIZE-1) - x - (sizeX - 1) };
+ }
+ if (chunkRotation == 2) {
+ return new int[] { (SIZE-1) - x - (sizeX - 1), 7 - y - (sizeY - 1) };
+ }
+ return new int[] { (SIZE-1) - y - (sizeY - 1), x };
+ }
+
+ public ChunkFlags getFlags() {
+ return flags;
+ }
+
+ public ChunkFlags getProjectileFlags() {
+ return projectileFlags;
+ }
+
+ public Region getRegion() {
+ return RegionManager.forId(currentBase.getRegionId());
+ }
}
diff --git a/Server/src/main/core/game/world/map/RegionManager.kt b/Server/src/main/core/game/world/map/RegionManager.kt
index 9fb7b848b..d34cc2d47 100644
--- a/Server/src/main/core/game/world/map/RegionManager.kt
+++ b/Server/src/main/core/game/world/map/RegionManager.kt
@@ -9,11 +9,9 @@ import core.game.node.scenery.Scenery
import core.game.world.map.zone.ZoneBorders
import core.tools.Log
import core.tools.RandomFunction
-import core.tools.SystemLogger
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLock
-import kotlin.collections.HashMap
/**
* Manages the regions.
@@ -27,7 +25,7 @@ object RegionManager {
@JvmStatic val CLIPPING_FLAGS = HashMap>()
@JvmStatic val PROJECTILE_FLAGS = HashMap>()
- public val LOCK = ReentrantLock()
+ private val LOCK = ReentrantLock()
/**
* Gets the region for the given region id.
@@ -36,7 +34,7 @@ object RegionManager {
*/
@JvmStatic
fun forId(regionId: Int): Region {
- if(LOCK.tryLock() || LOCK.tryLock(10000, TimeUnit.MILLISECONDS)) {
+ if (LOCK.tryLock() || LOCK.tryLock(10000, TimeUnit.MILLISECONDS)) {
var region = REGION_CACHE[regionId]
if (region == null) {
region = Region((regionId shr 8) and 0xFF, regionId and 0xFF)
@@ -45,37 +43,25 @@ object RegionManager {
LOCK.unlock()
return REGION_CACHE[regionId]!!
}
- log(this::class.java, Log.ERR, "UNABLE TO OBTAIN LOCK WHEN GETTING REGION BY ID. RETURNING BLANK REGION.")
- return Region(0,0)
+ log(this::class.java, Log.ERR, "UNABLE TO OBTAIN LOCK WHEN GETTING REGION BY ID. RETURNING BLANK REGION.")
+ return Region(0, 0)
}
/**
- * Pulses the active regions.
+ * Applies a function to all active regions
*/
@JvmStatic
- fun pulse() {
- if(LOCK.tryLock() || LOCK.tryLock(10000,TimeUnit.MILLISECONDS)) {
+ fun apply(callback: (r: Region) -> Unit) {
+ if (LOCK.tryLock() || LOCK.tryLock(10000,TimeUnit.MILLISECONDS)) {
for (r in REGION_CACHE.values) {
if (r.isActive) {
- for (p in r.planes) {
- p.pulse()
- }
+ callback(r)
}
}
LOCK.unlock()
}
}
- /**
- * Gets the clipping flag on the given location.
- * @param l The location.
- * @return The clipping flag.
- */
- @JvmStatic
- fun getClippingFlag(l: Location): Int {
- return getClippingFlag(l.z, l.x, l.y)
- }
-
/**
* Gets the clipping flag.
* @param z The plane.
@@ -85,56 +71,36 @@ object RegionManager {
*/
@JvmStatic
fun getClippingFlag(z: Int, x: Int, y: Int): Int {
- val regionX = x shr 6
- val regionY = y shr 6
- val localX = x and 63
- val localY = y and 63
- return getClippingFlag(z, regionX, regionY, localX, localY)
+ return getClippingFlag(Location(x, y, z))
}
/**
- * Gets the clipping flags using Jagex-style coords
- * e.g 0_50_50_13_13 gets plane 0, region 50-50 (12850), (13, 13) which is in lumbridge.
+ * Gets the clipping flag.
+ * @param loc The location.
+ * @param projectile Clipping flags or projectile flags.
*/
@JvmStatic
- fun getClippingFlag(z: Int, regionX: Int, regionY: Int, localX: Int, localY: Int, projectile: Boolean = false) : Int {
- val (region, index) = getFlagIndex(z, regionX, regionY, localX, localY)
- var flag = getFlags(region, projectile)[index]
-
- if (flag == -1) {
- val r = forId((regionX shr 8) or regionY)
- if (!r.isLoaded)
- Region.load(r)
- if (!r.isHasFlags)
- return -1
- flag = getFlags(region, projectile)[index]
+ fun getClippingFlag(loc: Location, projectile: Boolean = false) : Int {
+ val region = forId(loc.regionId)
+ Region.load(region)
+ if (!region.isHasFlags) {
+ return -1
}
-
- return flag
+ // Index by absChunkX and absChunkY coordinates; these give the position of the chunk on the world map. Together with chunkOffsetX and chunkOffsetY, they identify individual tiles.
+ val (key, index) = getFlagIndex(loc.absChunkX, loc.absChunkY, loc.z, loc.chunkOffsetX, loc.chunkOffsetY)
+ return getFlags(key, projectile)[index]
}
- private fun getFlagIndex(z: Int, regionX: Int, regionY: Int, localX: Int, localY: Int) : Pair {
- return Pair((regionX shl 8) or regionY, (z * 64 * 64) + (localX * 64) + localY)
+ private fun getFlagIndex(absChunkX: Int, absChunkY: Int, z: Int, chunkOffsetX: Int, chunkOffsetY: Int) : Pair {
+ return Pair((z shl 22) or (absChunkX shl 11) or absChunkY, (chunkOffsetX * 8) + chunkOffsetY)
}
@JvmStatic
- fun getFlags(regionX: Int, regionY: Int, projectile: Boolean) : Array {
- val region = (regionX shl 8) or regionY
- return getFlags(region, projectile)
- }
-
- @JvmStatic
- fun getFlags(regionId: Int, projectile: Boolean) : Array {
+ fun getFlags(chunkId: Int, projectile: Boolean) : Array {
return if (projectile)
- PROJECTILE_FLAGS.getOrPut (regionId) {Array(16384){0}}
+ PROJECTILE_FLAGS.getOrPut(chunkId) { Array(64){0} }
else
- CLIPPING_FLAGS.getOrPut (regionId) {Array(16384){-1}}
- }
-
- @JvmStatic
- fun resetFlags(regionId: Int) {
- PROJECTILE_FLAGS.put (regionId, Array(16384){0})
- CLIPPING_FLAGS.put (regionId, Array(16384){-1})
+ CLIPPING_FLAGS.getOrPut(chunkId) { Array(64){-1} }
}
/**
@@ -144,41 +110,37 @@ object RegionManager {
*/
@JvmStatic
fun getWaterClipFlag(z: Int, x: Int, y: Int): Int {
- val flag = getClippingFlag(z, x, y)
- return if (!isClipped(z, x, y)) {
+ return getWaterClipFlag(Location(x, y, z))
+ }
+
+ /**
+ * Gets the water variant of a tile's clipping flag
+ * Essentially strips the landscape flag off a tile and keeps other flags, and makes normally walkable tiles unwalkable.
+ */
+ @JvmStatic
+ fun getWaterClipFlag(loc: Location): Int {
+ val flag = getClippingFlag(loc)
+ return if (!isClipped(loc)) {
flag or 0x100
} else flag and 0x200000.inv()
}
/**
* Checks if the tile is part of the landscape.
- * @param l The location.
+ * @param loc The location.
* @return `True` if so.
*/
@JvmStatic
- fun isLandscape(l: Location): Boolean {
- return isLandscape(l.z, l.x, l.y)
- }
-
- /**
- * Checks if the tile is part of the landscape.
- * @param z The plane.
- * @param x The absolute x-coordinate.
- * @param y The absolute y-coordinate.
- * @return `True` if so.
- */
- @JvmStatic
- fun isLandscape(z: Int, x: Int, y: Int): Boolean {
- var x = x
- var y = y
- val region = forId(((x shr 6) shl 8) or (y shr 6))
+ fun isLandscape(loc: Location): Boolean {
+ val region = forId(loc.regionId)
Region.load(region)
- if (!region.isHasFlags || region.planes[z].flags.landscape == null) {
+ if (!region.isHasFlags) {
return false
}
- x -= x shr 6 shl 6
- y -= y shr 6 shl 6
- return region.planes[z].flags.landscape[x][y]
+ if (loc.chunk.flags.landscape == null) {
+ return false
+ }
+ return loc.chunk.flags.landscape[loc.chunkOffsetX][loc.chunkOffsetY]
}
/**
@@ -191,24 +153,31 @@ object RegionManager {
*/
@JvmStatic
fun addClippingFlag(z: Int, x: Int, y: Int, projectile: Boolean, flag: Int) {
- var x = x
- var y = y
- val region = forId(((x shr 6) shl 8) or (y shr 6))
- Region.load(region)
- if (!region.isHasFlags) {
- return
- }
- x -= (x shr 6) shl 6
- y -= (y shr 6) shl 6
- if (projectile) {
- region.planes[z].projectileFlags.flag(x, y, flag)
- } else {
- region.planes[z].flags.flag(x, y, flag)
- }
+ return addClippingFlag(Location(x, y, z), projectile, flag)
}
/**
* Adds a clipping flag.
+ * @param loc The location.
+ * @param projectile If the flag is being set for projectile pathfinding.
+ * @param flag The clipping flag.
+ */
+ @JvmStatic
+ fun addClippingFlag(loc: Location, projectile: Boolean, flag: Int) {
+ val region = forId(loc.regionId)
+ Region.load(region)
+ if (!region.isHasFlags) {
+ return
+ }
+ if (projectile) {
+ loc.chunk.projectileFlags.flag(loc.chunkOffsetX, loc.chunkOffsetY, flag)
+ } else {
+ loc.chunk.flags.flag(loc.chunkOffsetX, loc.chunkOffsetY, flag)
+ }
+ }
+
+ /**
+ * Removes a clipping flag.
* @param z The plane.
* @param x The absolute x-coordinate.
* @param y The absolute y-coordinate.
@@ -217,24 +186,31 @@ object RegionManager {
*/
@JvmStatic
fun removeClippingFlag(z: Int, x: Int, y: Int, projectile: Boolean, flag: Int) {
- var x = x
- var y = y
- val region = forId(((x shr 6) shl 8) or (y shr 6))
+ return removeClippingFlag(Location(x, y, z), projectile, flag)
+ }
+
+ /**
+ * Removes a clipping flag.
+ * @param loc The location.
+ * @param projectile If the flag is being set for projectile pathfinding.
+ * @param flag The clipping flag.
+ */
+ @JvmStatic
+ fun removeClippingFlag(loc: Location, projectile: Boolean, flag: Int) {
+ val region = forId(loc.regionId)
Region.load(region)
if (!region.isHasFlags) {
return
}
- x -= (x shr 6) shl 6
- y -= (y shr 6) shl 6
if (projectile) {
- region.planes[z].projectileFlags.unflag(x, y, flag)
+ loc.chunk.projectileFlags.unflag(loc.chunkOffsetX, loc.chunkOffsetY, flag)
} else {
- region.planes[z].flags.unflag(x, y, flag)
+ loc.chunk.flags.unflag(loc.chunkOffsetX, loc.chunkOffsetY, flag)
}
}
/**
- * Gets the clipping flag.
+ * Gets the projectile flag.
* @param z The plane.
* @param x The absolute x-coordinate.
* @param y The absolute y-coordinate.
@@ -242,62 +218,34 @@ object RegionManager {
*/
@JvmStatic
fun getProjectileFlag(z: Int, x: Int, y: Int): Int {
- val regionX = x shr 6
- val regionY = y shr 6
- val localX = x and 63
- val localY = y and 63
- return getClippingFlag(z, regionX, regionY, localX, localY, true)
+ return getClippingFlag(Location(x, y, z), true)
}
/**
- * Gets the clipping flag
- * @param location the Location
- * @return the clipping flag
+ * Checks if teleport is permitted.
+ * @param loc The Location.
+ * @return If teleport is permitted.
*/
@JvmStatic
- fun isTeleportPermitted(location: Location): Boolean {
- return isTeleportPermitted(location.z, location.x, location.y)
- }
-
- /**
- * Gets the clipping flag.
- * @param z The plane.
- * @param x The absolute x-coordinate.
- * @param y The absolute y-coordinate.
- * @return The clipping flags.
- */
- @JvmStatic
- fun isTeleportPermitted(z: Int, x: Int, y: Int): Boolean {
- if (!isLandscape(z, x, y)) {
+ fun isTeleportPermitted(loc: Location): Boolean {
+ if (!isLandscape(loc)) {
return false
}
- val flag = getClippingFlag(z, x, y)
+ val flag = getClippingFlag(loc)
return flag and 0x12c0102 == 0 || flag and 0x12c0108 == 0 || flag and 0x12c0120 == 0 || flag and 0x12c0180 == 0
}
/**
* Checks if the location has any clipping flags.
- * @param location The location.
+ * @param loc The location.
* @return `True` if a clipping flag disables access for this location.
*/
@JvmStatic
- fun isClipped(location: Location): Boolean {
- return isClipped(location.z, location.x, location.y)
- }
-
- /**
- * Checks if the location has any clipping flags.
- * @param z The plane.
- * @param x The x-coordinate.
- * @param y The y-coordinate.
- * @return `True` if a clipping flag disables access for this location.
- */
- @JvmStatic
- fun isClipped(z: Int, x: Int, y: Int): Boolean {
- if (!isLandscape(z, x, y)) {
+ fun isClipped(loc: Location): Boolean {
+ if (!isLandscape(loc)) {
return true
}
- val flag = getClippingFlag(z, x, y)
+ val flag = getClippingFlag(loc)
return flag and 0x12c0102 != 0 || flag and 0x12c0108 != 0 || flag and 0x12c0120 != 0 || flag and 0x12c0180 != 0
}
@@ -352,53 +300,45 @@ object RegionManager {
*/
@JvmStatic
fun getObject(l: Location): Scenery? {
- return getObject(l.z, l.x, l.y)
+ return getObject(l.x, l.y, l.z)
}
/**
* Gets the scenery on the current absolute coordinates.
- * @param z The height.
* @param x The x-coordinate.
* @param y The y-coordinate.
+ * @param z The height.
* @return The scenery, or `null` if no object was found.
*/
@JvmStatic
- fun getObject(z: Int, x: Int, y: Int): Scenery? {
- return getObject(z, x, y, -1)
+ fun getObject(x: Int, y: Int, z: Int): Scenery? {
+ return getObject(x, y, z, -1, -1)
}
/**
* Gets the object on the given absolute coordinates.
- * @param z The height.
* @param x The x-coordinate.
* @param y The y-coordinate.
- * @param objectId The object id.
+ * @param z The height.
+ * @param objectId The object id. May be -1, which means 'any'.
+ * @param type The scenery type. May be -1, which means 'any'.
* @return The scenery, or `null` if no object was found.
*/
@JvmStatic
- fun getObject(z: Int, x: Int, y: Int, objectId: Int): Scenery? {
- var x = x
- var y = y
- val regionId = ((x shr 6) shl 8) or (y shr 6)
- x -= (x shr 6) shl 6
- y -= (y shr 6) shl 6
- val region = forId(regionId)
+ fun getObject(x: Int, y: Int, z: Int, objectId: Int = -1, type: Int = -1): Scenery? {
+ val loc = Location(x, y, z)
+ val region = forId(loc.regionId)
Region.load(region)
- val `object`: Scenery? = region.planes[z].getChunkObject(x, y, objectId)
- return if (`object` != null && !`object`.isRenderable) {
- null
- } else `object`
- }
-
- /**
- * Gets the region plane for this location.
- * @param l The location.
- * @return The region plane.
- */
- @JvmStatic
- fun getRegionPlane(l: Location): RegionPlane {
- val regionId = ((l.x shr 6) shl 8) or (l.y shr 6)
- return forId(regionId).planes[l.z]
+ val chunk = region.chunks[loc.chunkX][loc.chunkY][loc.z]
+ val index = chunk.getIndex(loc.chunkOffsetX, loc.chunkOffsetY, objectId, type)
+ if (index == -1) {
+ return null
+ }
+ val obj = chunk.objects[loc.chunkOffsetX][loc.chunkOffsetY][index]
+ if (obj != null && !obj.isRenderable) {
+ return null
+ }
+ return obj
}
/**
@@ -408,247 +348,73 @@ object RegionManager {
*/
@JvmStatic
fun getRegionChunk(l: Location): RegionChunk {
- val plane = getRegionPlane(l)
- return plane.getRegionChunk(l.localX / RegionChunk.SIZE, l.localY / RegionChunk.SIZE)
+ val regionId = ((l.x shr 6) shl 8) or (l.y shr 6)
+ return forId(regionId).chunks[l.chunkX][l.chunkY][l.z]
}
/**
- * Moves the entity from the current region to the new one.
+ * Moves the entity from the current chunk to the new one.
* @param entity The entity.
*/
@JvmStatic
- fun move(entity: Entity) {
- val player = entity is Player
- val regionId = ((entity.location.regionX shr 3) shl 8) or (entity.location.regionY shr 3)
- val viewport = entity.viewport
- val current = forId(regionId)
- val z = entity.location.z
- val plane = current.planes[z]
- viewport.updateViewport(entity)
- if (plane == viewport.currentPlane) {
- entity.zoneMonitor.updateLocation(entity.walkingQueue.footPrint)
- return
- }
- viewport.remove(entity)
- if (player) {
- current.add(entity as Player)
- } else {
- current.add(entity as NPC)
- }
- viewport.region = current
- viewport.currentPlane = plane
- val view: MutableList = LinkedList()
- for (regionX in ((entity.location.regionX shr 3) - 1)..((entity.location.regionX shr 3) + 1)) {
- for (regionY in ((entity.location.regionY shr 3) - 1)..((entity.location.regionY shr 3) + 1)) {
- if (regionX < 0 || regionY < 0) {
- continue
+ fun move(entity: Entity, src: Location?, dst: Location) {
+ if (src?.chunk != dst.chunk) {
+ when (entity) {
+ is Player -> {
+ val player = entity.asPlayer()
+ val sameRegion = src?.region == dst.region
+ if (src != null && sameRegion) {
+ src.chunk.removePlayer(player)
+ dst.chunk.addPlayer(player)
+ } else {
+ if (src != null) {
+ src.chunk.removePlayer(player)
+ src.region.tolerances.remove(player.name)
+ src.region.decrementViewAmount()
+ }
+ dst.chunk.addPlayer(entity)
+ dst.region.tolerances[entity.asPlayer().name] = System.currentTimeMillis()
+ dst.region.incrementViewAmount()
+ }
}
- val region = forId((regionX shl 8) or regionY)
- val p = region.planes[z]
- if (player) {
- region.incrementViewAmount()
- region.flagActive()
+ is NPC -> {
+ src?.chunk?.remove(entity)
+ dst.chunk.add(entity)
}
- view.add(p)
+ else -> log(this::class.java, Log.ERR, "Tried to move an Entity that was neither Player nor NPC, but $entity of class ${entity.javaClass}! It tried to move from $src to $dst.")
}
}
- viewport.viewingPlanes = view
entity.zoneMonitor.updateLocation(entity.walkingQueue.footPrint)
}
/**
- * Gets the list of local NPCs with a maximum distance of 16.
- * @param n The entity.
- * @return The list of local NPCs.
- */
- @JvmStatic
- fun getLocalNpcs(n: Entity): List {
- return getLocalNpcs(n, MapDistance.RENDERING.distance)
- }
-
- /**
- * Gets the location entitys.
+ * Gets local entities. You never call the below function directly (it's inlined); instead, you use one of the typed helpers defined directly below this function.
* @param location the location.
* @param distance the distance.
* @return the list.
*/
- @JvmStatic
- fun getLocalEntitys(location: Location, distance: Int): List {
- val entitys: MutableList = ArrayList(20)
- entitys.addAll(getLocalNpcs(location, distance))
- entitys.addAll(getLocalPlayers(location, distance))
- return entitys
- }
-
- /**
- * Gets the location entitys.
- * @param entity the entity.
- * @param distance the distance.
- * @return the list.
- */
- @JvmStatic
- fun getLocalEntitys(entity: Entity, distance: Int): List {
- return getLocalEntitys(entity.location, distance)
- }
-
- /**
- * Gets the local entitys.
- * @param entity the entity.
- * @return the entitys.
- */
- @JvmStatic
- fun getLocalEntitys(entity: Entity): List {
- return getLocalEntitys(entity.location, MapDistance.RENDERING.distance)
- }
-
- /**
- * Gets the list of local NPCs.
- * @param n The entity.
- * @param distance The distance to the entity.
- * @return The list of local NPCs.
- */
- @JvmStatic
- fun getLocalNpcs(n: Entity, distance: Int): List {
- val npcs: MutableList = LinkedList()
- for (r in n.viewport.viewingPlanes) {
- for (npc in r.npcs) {
- if (npc.location.withinDistance(n.location, distance)) {
- npcs.add(npc)
+ private inline fun getLocalEntitiesOfType(location: Location, distance: Int): List {
+ val entities: ArrayList = ArrayList(20)
+ val radius = (distance / 8) + 1
+ for (cx in (location.absChunkX - radius)..(location.absChunkX + radius)) {
+ for (cy in (location.absChunkY - radius)..(location.absChunkY + radius)) {
+ val chunk = Location(cx shl 3, cy shl 3, location.z).chunk
+ if (T::class.java.isAssignableFrom(Player::class.java)) {
+ entities.addAll(chunk.players.filterIsInstance())
+ }
+ if (T::class.java.isAssignableFrom(NPC::class.java)) {
+ entities.addAll(chunk.npcs.filterIsInstance())
}
}
}
- return npcs
- }
-
- /**
- * Gets the list of local players with a maximum distance of 15.
- * @param n The entity.
- * @return The list of local players.
- */
- @JvmStatic
- fun getLocalPlayers(n: Entity): List {
- return getLocalPlayers(n, MapDistance.RENDERING.distance)
- }
-
- /**
- * Gets the list of local players.
- * @param n The entity.
- * @param distance The distance to the entity.
- * @return The list of local players.
- */
- @JvmStatic
- fun getLocalPlayers(n: Entity, distance: Int): List {
- val players: MutableList = LinkedList()
- for (r in n.viewport.viewingPlanes) {
- for (p in r.players) {
- if (p.location.withinDistance(n.location, distance)) {
- players.add(p)
- }
- }
- }
- return players
- }
-
- /**
- * Gets the surrounding players.
- * @param n The node the players should be surrounding.
- * @param ignore The nodes not to add to the list.
- * @return The list of players.
- */
- @JvmStatic
- fun getSurroundingPlayers(n: Node, vararg ignore: Node): List {
- return getSurroundingPlayers(n, 9, *ignore)
- }
-
- /**
- * Gets the surrounding players.
- * @param n The node the players should be surrounding.
- * @param ignore The nodes not to add to the list.
- * @return The list of players.
- */
- @JvmStatic
- fun getSurroundingPlayers(n: Node, maximum: Int, vararg ignore: Node): List {
- val players = getLocalPlayers(n.location, 2)
- var count = 0
- val it = players.iterator()
- while (it.hasNext()) {
- val p = it.next()
- if(p.isInvisible()) {
- it.remove()
- }
- if(!p.location.withinMaxnormDistance(n.location, 1)) {
- it.remove()
- continue
- }
- if (++count >= maximum) {
- it.remove()
- continue
- }
- for (node in ignore) {
- if (p === node) {
- count--
- it.remove()
- break
- }
- }
- }
- return players
- }
-
- /**
- * Gets the surrounding players.
- * @param n The node the players should be surrounding.
- * @param ignore The nodes not to add to the list.
- * @return The list of players.
- */
- @JvmStatic
- fun getSurroundingNPCs(n: Node, vararg ignore: Node): List {
- return getSurroundingNPCs(n, 9, *ignore)
- }
-
- /**
- * Gets the surrounding players.
- * @param n The node the npcs should be surrounding.
- * @param ignore The nodes not to add to the list.
- * @return The list of npcs.
- */
- @JvmStatic
- fun getSurroundingNPCs(n: Node, maximum: Int, vararg ignore: Node): List {
- val npcs = getLocalNpcs(n.location, 2)
- var count = 0
- val it = npcs.iterator()
- while (it.hasNext()) {
- val p = it.next()
- if(p.properties.teleportLocation != null && !p.properties.teleportLocation.withinMaxnormDistance(n.location, 1)) {
- it.remove()
- continue
- }
- if(p.getAttribute("state:death", false)) {
- it.remove()
- continue
- }
- if(p.isInvisible()) {
- it.remove()
- continue
- }
- if(!p.location.withinMaxnormDistance(n.location, 1)) {
- it.remove()
- continue
- }
- if (++count > maximum) {
- it.remove()
- continue
- }
- for (node in ignore) {
- if (p === node) {
- count--
- it.remove()
- break
- }
- }
- }
- return npcs
+ val b = ZoneBorders(location.x - distance, location.y - distance, location.x + distance, location.y + distance)
+ return entities.filter { b.insideBorder(it.location.x, it.location.y) }
}
+ @JvmStatic fun getLocalEntities(location: Location, distance: Int) = getLocalEntitiesOfType(location, distance)
+ @JvmStatic fun getLocalPlayers(location: Location, distance: Int) = getLocalEntitiesOfType(location, distance)
+ @JvmStatic fun getLocalNPCs(location: Location, distance: Int) = getLocalEntitiesOfType(location, distance)
+ @JvmStatic fun getLocalPlayers(location: Location): List = getLocalPlayers(location, MapDistance.RENDERING.distance)
+ @JvmStatic fun getLocalNPCs(location: Location): List = getLocalNPCs(location, MapDistance.RENDERING.distance)
/**
* Gets a random teleport location in the radius around the given location.
@@ -701,162 +467,14 @@ object RegionManager {
}
/**
- * Gets the current viewport for the location.
- * @param l The location.
- * @return The viewport.
- */
- @JvmStatic
- fun getViewportPlayers(l: Location): List