diff --git a/Server/src/main/java/core/game/node/entity/skill/construction/BuildingUtils.java b/Server/src/main/java/core/game/node/entity/skill/construction/BuildingUtils.java
index 9a9dd5842..672896d53 100644
--- a/Server/src/main/java/core/game/node/entity/skill/construction/BuildingUtils.java
+++ b/Server/src/main/java/core/game/node/entity/skill/construction/BuildingUtils.java
@@ -166,7 +166,7 @@ public final class BuildingUtils {
int roomX = object.getLocation().getChunkX();
int roomY = object.getLocation().getChunkY();
int z = object.getLocation().getZ();
- Region region = player.getHouseManager().getRegion();
+ Region region = player.getHouseManager().getHouseRegion();
if (HouseManager.isInDungeon(player)) {
region = player.getHouseManager().getDungeonRegion();
z = 3;
@@ -265,7 +265,7 @@ public final class BuildingUtils {
Hotspot h = r.getStairs();
if (h != null) {
h.setDecorationIndex(decIndex);
- Region reg = plane == 3 ? player.getHouseManager().getDungeonRegion() : player.getHouseManager().getRegion();
+ Region reg = plane == 3 ? player.getHouseManager().getDungeonRegion() : player.getHouseManager().getHouseRegion();
if (reg == null) {
continue;
}
@@ -352,7 +352,7 @@ public final class BuildingUtils {
}
Location l = object.getLocation();
Room room = player.getHouseManager().getRooms()[l.getZ()][l.getChunkX()][l.getChunkY()];
- Region region = player.getHouseManager().getRegion();
+ Region region = player.getHouseManager().getHouseRegion();
if (HouseManager.isInDungeon(player)) {
region = player.getHouseManager().getDungeonRegion();
room = player.getHouseManager().getRooms()[3][l.getChunkX()][l.getChunkY()];
@@ -396,7 +396,7 @@ public final class BuildingUtils {
Hotspot h = r.getStairs();
if (h != null) {
h.setDecorationIndex(-1);
- Region reg = plane == 3 ? player.getHouseManager().getDungeonRegion() : player.getHouseManager().getRegion();
+ Region reg = plane == 3 ? player.getHouseManager().getDungeonRegion() : player.getHouseManager().getHouseRegion();
if (reg == null) {
continue;
}
@@ -589,7 +589,7 @@ public final class BuildingUtils {
if (HouseManager.isInDungeon(player)) {
z = 3;
}
- if (player.getHouseManager().hasRoom(z, location[0], location[1])) {
+ if (player.getHouseManager().hasRoomAt(z, location[0], location[1])) {
return location;
}
return null;
@@ -666,7 +666,7 @@ public final class BuildingUtils {
else if (player.getHouseManager().hasExit(z, roomX - 1, roomY, Direction.EAST)) {
exits[2] = 1;
}
- else if (player.getHouseManager().hasRoom(z, roomX - 1, roomY)) {
+ else if (player.getHouseManager().hasRoomAt(z, roomX - 1, roomY)) {
exits[2] = -1;
}
if (roomY == 7) {
@@ -675,7 +675,7 @@ public final class BuildingUtils {
else if (player.getHouseManager().hasExit(z, roomX, roomY + 1, Direction.SOUTH)) {
exits[3] = 1;
}
- else if (player.getHouseManager().hasRoom(z, roomX, roomY + 1)) {
+ else if (player.getHouseManager().hasRoomAt(z, roomX, roomY + 1)) {
exits[3] = -1;
}
if (roomX == 7) {
@@ -684,7 +684,7 @@ public final class BuildingUtils {
else if (player.getHouseManager().hasExit(z, roomX + 1, roomY, Direction.WEST)) {
exits[0] = 1;
}
- else if (player.getHouseManager().hasRoom(z, roomX + 1, roomY)) {
+ else if (player.getHouseManager().hasRoomAt(z, roomX + 1, roomY)) {
exits[0] = -1;
}
if (roomY == 0) {
@@ -693,7 +693,7 @@ public final class BuildingUtils {
else if (player.getHouseManager().hasExit(z, roomX, roomY - 1, Direction.NORTH)) {
exits[1] = 1;
}
- else if (player.getHouseManager().hasRoom(z, roomX, roomY - 1)) {
+ else if (player.getHouseManager().hasRoomAt(z, roomX, roomY - 1)) {
exits[1] = -1;
}
return exits;
diff --git a/Server/src/main/java/core/game/node/entity/skill/construction/HouseManager.java b/Server/src/main/java/core/game/node/entity/skill/construction/HouseManager.java
index c2a912fbb..a5bf99e30 100644
--- a/Server/src/main/java/core/game/node/entity/skill/construction/HouseManager.java
+++ b/Server/src/main/java/core/game/node/entity/skill/construction/HouseManager.java
@@ -4,6 +4,8 @@ package core.game.node.entity.skill.construction;
//import org.arios.game.content.global.DeadmanTimedAction;
//import org.arios.game.node.entity.player.info.login.SavingModule;
+import api.regionspec.RegionSpecification;
+import api.regionspec.contracts.FillChunkContract;
import core.game.content.dialogue.FacialExpression;
import core.game.node.entity.player.Player;
import core.game.node.entity.player.link.audio.Audio;
@@ -15,6 +17,8 @@ import core.game.world.map.build.DynamicRegion;
import core.game.world.map.zone.ZoneBorders;
import core.game.world.map.zone.ZoneBuilder;
import core.game.world.update.flag.context.Animation;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import rs09.game.node.entity.skill.construction.Hotspot;
@@ -24,6 +28,9 @@ import rs09.game.world.GameWorld;
import java.awt.*;
import java.nio.ByteBuffer;
+import static api.regionspec.RegionSpecificationKt.fillWith;
+import static api.regionspec.RegionSpecificationKt.using;
+
/**
* Manages the player's house.
* @author Emperor
@@ -34,12 +41,12 @@ public final class HouseManager {
/**
* The current region.
*/
- private DynamicRegion region;
+ private DynamicRegion houseRegion;
/**
* The current region.
*/
- private DynamicRegion dungeon;
+ private DynamicRegion dungeonRegion;
/**
* The house location.
@@ -174,58 +181,73 @@ public final class HouseManager {
}
}
- /**
- * Enters the player's house.
- * @param player The player entering.
- * @param buildingMode If building mode is enabled.
- * @param teleport if the entry was a teleport.
- */
- public void enter(final Player player, boolean buildingMode, boolean teleport) {
- enter(player, buildingMode);
- }
-
/**
* Enter's the player's house.
* @param player
* @param buildingMode
*/
public void enter(final Player player, boolean buildingMode) {
- if (HouseManager.this.buildingMode != buildingMode || !isLoaded()) {
- HouseManager.this.buildingMode = buildingMode;
+ if (this.buildingMode != buildingMode || !isLoaded()) {
+ this.buildingMode = buildingMode;
construct();
}
player.setAttribute("poh_entry", HouseManager.this);
player.lock(1);
- player.sendMessage("House location: " + region.getBaseLocation() + ", entry: " + getEnterLocation());
+ player.sendMessage("House location: " + houseRegion.getBaseLocation() + ", entry: " + getEnterLocation());
player.getProperties().setTeleportLocation(getEnterLocation());
+ openLoadInterface(player);
+ checkForAndSpawnServant(player);
+ updateVarbits(player, buildingMode);
+ unlockMusicTrack(player);
+ }
+
+ private void openLoadInterface(Player player) {
player.getInterfaceManager().openComponent(399);
- player.getConfigManager().set(261, buildingMode);
- player.getConfigManager().set(262, getRoomAmount());
player.getAudioManager().send(new Audio(984));
- player.getMusicPlayer().unlock(454, true);
+ submitCloseLoadInterfacePulse(player);
+ }
+
+ private void submitCloseLoadInterfacePulse(Player player) {
GameWorld.getPulser().submit(new Pulse(1, player) {
@Override
public boolean pulse() {
- if (hasServant()){
- spawnServant();
- if (servant.isGreet()){
- player.getDialogueInterpreter().sendDialogues(servant.getType().getId(), servant.getType().getId() == 4243 ? FacialExpression.HALF_GUILTY : null, "Welcome.");
- }
- }
-// player.getInterfaceManager().switchWindowMode(1);
player.getInterfaceManager().close();
return true;
}
});
}
+ private void checkForAndSpawnServant(Player player) {
+ if(!hasServant()) return;
+
+ GameWorld.getPulser().submit(new Pulse(1, player) {
+ @Override
+ public boolean pulse() {
+ spawnServant();
+ if (servant.isGreet()){
+ player.getDialogueInterpreter().sendDialogues(servant.getType().getId(), servant.getType().getId() == 4243 ? FacialExpression.HALF_GUILTY : null, "Welcome.");
+ }
+ return true;
+ }
+ });
+ }
+
+ private void updateVarbits(Player player, boolean build) {
+ player.varpManager.get(261).setVarbit(0, build ? 1 : 0);
+ player.varpManager.get(262).setVarbit(0, getRoomAmount());
+ }
+
+ private void unlockMusicTrack(Player player) {
+ player.getMusicPlayer().unlock(454, true);
+ }
+
/**
* Leaves this house.
* @param player The player leaving.
*/
public static void leave(Player player) {
HouseManager house = player.getAttribute("poh_entry", player.getHouseManager());
- if (house.getRegion() == null){
+ if (house.getHouseRegion() == null){
return;
}
if (house.isInHouse(player)) {
@@ -260,17 +282,14 @@ public final class HouseManager {
* @param buildingMode If building mode should be enabled.
*/
public void reload(Player player, boolean buildingMode) {
- DynamicRegion r = region;
- if ((player.getViewport().getRegion() == dungeon)) {
- r = dungeon;
- }
- int diffX = player.getLocation().getX() - r.getBaseLocation().getX();
- int diffY = player.getLocation().getY() - r.getBaseLocation().getY();
- int diffZ = player.getLocation().getZ() - r.getBaseLocation().getZ();
- region = null;
- dungeon = null;
- enter(player, buildingMode, false);
- player.getProperties().setTeleportLocation((player.getViewport().getRegion() == dungeon ? dungeon : region).getBaseLocation().transform(diffX, diffY, diffZ));
+ int diffX = player.getLocation().getLocalX();
+ int diffY = player.getLocation().getLocalY();
+ int diffZ = player.getLocation().getZ();
+ boolean inDungeon = player.getViewport().getRegion() == dungeonRegion;
+ this.buildingMode = buildingMode;
+ construct();
+ Location newLoc = (dungeonRegion == null ? houseRegion : (inDungeon ? dungeonRegion : houseRegion)).getBaseLocation().transform(diffX,diffY,diffZ);
+ player.getProperties().setTeleportLocation(newLoc);
}
/**
@@ -279,15 +298,15 @@ public final class HouseManager {
*/
public void expelGuests(Player player) {
if (isLoaded()) {
- for (RegionPlane plane : region.getPlanes()) {
+ for (RegionPlane plane : houseRegion.getPlanes()) {
for (Player p : plane.getPlayers()) {
if (p != player) {
leave(p);
}
}
}
- if (dungeon != null) {
- for (RegionPlane plane : dungeon.getPlanes()) {
+ if (dungeonRegion != null) {
+ for (RegionPlane plane : dungeonRegion.getPlanes()) {
for (Player p : plane.getPlayers()) {
if (p != player) {
leave(p);
@@ -303,7 +322,7 @@ public final class HouseManager {
* @return The entering location.
*/
public Location getEnterLocation() {
- if (region == null) {
+ if (houseRegion == null) {
SystemLogger.logErr("House wasn't constructed yet!");
return null;
}
@@ -315,7 +334,7 @@ public final class HouseManager {
if (h.getDecorationIndex() > -1) {
Decoration d = h.getHotspot().getDecorations()[h.getDecorationIndex()];
if (d == Decoration.PORTAL) {
- return region.getBaseLocation().transform(x * 8 + h.getChunkX(), y * 8 + h.getChunkY() + 2, 0);
+ return houseRegion.getBaseLocation().transform(x * 8 + h.getChunkX(), y * 8 + h.getChunkY() + 2, 0);
}
}
}
@@ -361,7 +380,7 @@ public final class HouseManager {
* Creates the default house.
* @param location The house location.
*/
- public void create(HouseLocation location) {
+ public void createNewHouseAt(HouseLocation location) {
clearRooms();
Room room = rooms[0][4][3] = new Room(RoomProperties.GARDEN);
room.configure(style);
@@ -374,60 +393,100 @@ public final class HouseManager {
* @return The region.
*/
public DynamicRegion construct() {
+ houseRegion = getPreparedRegion();
+ configureRoofs();
+ prepareHouseChunks(style, houseRegion, buildingMode, rooms);
+
+ if (hasDungeon()) {
+ dungeonRegion = getPreparedRegion();
+ prepareDungeonChunks(style, dungeonRegion, houseRegion, buildingMode, rooms[3]);
+ }
+
+ ZoneBuilder.configure(zone);
+ return houseRegion;
+ }
+
+ private DynamicRegion getPreparedRegion() {
+ ZoneBorders borders = DynamicRegion.reserveArea(8,8);
+ DynamicRegion region = new DynamicRegion(-1, borders.getSouthWestX() >> 6, borders.getSouthWestY() >> 6);
+ region.setBorders(borders);
+ region.setUpdateAllPlanes(true);
+ RegionManager.addRegion(region.getId(), region);
+ return region;
+ }
+
+ private class RoomLoadContract extends FillChunkContract {
+ Room[][][] rooms;
+ HouseManager manager;
+ boolean buildingMode;
+
+ public RoomLoadContract(HouseManager manager, boolean buildingMode, Room[][][] rooms) {
+ this.rooms = rooms;
+ this.manager = manager;
+ this.buildingMode = buildingMode;
+ }
+
+ @Override
+ public BuildRegionChunk getChunk(int x, int y, int plane, @NotNull DynamicRegion dyn) {
+ BuildRegionChunk chunk = rooms[plane][x][y].getChunk().copy(dyn.getPlanes()[plane]);
+ return chunk;
+ }
+
+ @Override
+ public void afterSetting(@Nullable BuildRegionChunk chunk, int x, int y, int plane, @NotNull DynamicRegion dyn) {
+ rooms[plane][x][y].loadDecorations(dyn != manager.dungeonRegion ? plane : 3, chunk, manager);
+ }
+ }
+
+ private void prepareHouseChunks(HousingStyle style, DynamicRegion target, boolean buildingMode, Room[][][] rooms) {
Region from = RegionManager.forId(style.getRegionId());
Region.load(from, true);
RegionChunk defaultChunk = from.getPlanes()[style.getPlane()].getRegionChunk(1, 0);
RegionChunk defaultSkyChunk = from.getPlanes()[1].getRegionChunk(0,0);
- ZoneBorders borders = DynamicRegion.reserveArea(8,8);
- region = new DynamicRegion(-1, borders.getSouthWestX() >> 6, borders.getSouthWestY() >> 6);
- region.setBorders(borders);
- region.setUpdateAllPlanes(true);
- RegionManager.addRegion(region.getId(), region);
- configureRoofs();
- for (int z = 0; z < 4; z++) {
- for (int x = 0; x < 8; x++) {
- for (int y = 0; y < 8; y++) {
- if(z == 3){
- region.replaceChunk(z, x, y, defaultSkyChunk.copy(region.getPlanes()[z]), from);
- continue;
- }
- Room room = rooms[z][x][y];
- if (room != null) {
- if (room.getProperties().isRoof() && buildingMode) {
- continue;
- }
- BuildRegionChunk copy = room.getChunk().copy(region.getPlanes()[z]);
- region.replaceChunk(z, x, y, copy, from);
- room.loadDecorations(z, copy, this);
- } else {
- region.replaceChunk(z, x, y, z != 0 ? null : defaultChunk.copy(region.getPlanes()[0]), from);
- }
- }
- }
- }
- if (hasDungeon()) {
- defaultChunk = from.getPlanes()[style.getPlane()].getRegionChunk(3, 0);
- borders = DynamicRegion.reserveArea(8, 8);
- dungeon = new DynamicRegion(-1, borders.getSouthWestX() >> 6, borders.getSouthWestY() >> 6);
- dungeon.setBorders(borders);
- dungeon.setUpdateAllPlanes(true);
- RegionManager.addRegion(dungeon.getId(), dungeon);
- for (int x = 0; x < 8; x++) {
- for (int y = 0; y < 8; y++) {
- Room room = rooms[3][x][y];
- if (hasRoom(3, x, y)) {
- BuildRegionChunk copy = room.getChunk().copy(dungeon.getPlanes()[0]);
- dungeon.replaceChunk(0, x, y, copy, from);
- room.loadDecorations(3, copy, this);
- } else {
- dungeon.replaceChunk(0, x, y, buildingMode ? null : defaultChunk.copy(dungeon.getPlanes()[0]), from);
- }
- }
- }
- region.link(dungeon);
- }
- ZoneBuilder.configure(zone);
- return region;
+
+ RoomLoadContract loadRooms = new RoomLoadContract(this, buildingMode, rooms);
+ RegionSpecification spec = new RegionSpecification(
+ using(target),
+ fillWith(defaultChunk)
+ .from(from)
+ .onPlanes(0)
+ .onCondition((destX, destY, plane) -> rooms[plane][destX][destY] == null),
+ fillWith((RegionChunk) null)
+ .from(from)
+ .onPlanes(1,2)
+ .onCondition((destX, destY, plane) -> rooms[plane][destX][destY] == null),
+ fillWith(defaultSkyChunk)
+ .from(from)
+ .onPlanes(3),
+ loadRooms
+ .from(from)
+ .onPlanes(0,1,2)
+ .onCondition((destX,destY,plane) -> rooms[plane][destX][destY] != null)
+ );
+
+ spec.build();
+ }
+
+ private void prepareDungeonChunks(HousingStyle style, DynamicRegion target, DynamicRegion house, boolean buildingMode, Room[][] rooms) {
+ Region from = RegionManager.forId(style.getRegionId());
+ Region.load(from, true);
+ RegionChunk defaultChunk = from.getPlanes()[style.getPlane()].getRegionChunk(3, 0);
+
+ RoomLoadContract loadRooms = new RoomLoadContract(this, buildingMode, new Room[][][]{rooms});
+ RegionSpecification spec = new RegionSpecification(
+ using(target),
+ fillWith((x,y,plane,region) -> buildingMode ? null : defaultChunk)
+ .from(from)
+ .onPlanes(0)
+ .onCondition((destX, destY, plane) -> rooms[destX][destY] == null),
+ loadRooms
+ .from(from)
+ .onPlanes(0)
+ .onCondition((destX, destY, plane) -> rooms[destX][destY] != null)
+ );
+
+ spec.build();
+ house.link(target);
}
/**
@@ -457,7 +516,7 @@ public final class HouseManager {
*/
public Room getRoom(Location l) {
int z = l.getZ();
- if (dungeon != null && l.getRegionId() == dungeon.getId()) {
+ if (dungeonRegion != null && l.getRegionId() == dungeonRegion.getId()) {
z = 3;
}
return rooms[z][l.getChunkX()][l.getChunkY()];
@@ -512,7 +571,7 @@ public final class HouseManager {
* @param roomY The room y-coordinate.
* @return {@code True} if so.
*/
- public boolean hasRoom(int z, int roomX, int roomY) {
+ public boolean hasRoomAt(int z, int roomX, int roomY) {
Room room = rooms[z][roomX][roomY];
return room != null && !room.getProperties().isRoof();
}
@@ -527,7 +586,7 @@ public final class HouseManager {
}
int diffX = player.getLocation().getLocalX();
int diffY = player.getLocation().getLocalY();
- player.getProperties().setTeleportLocation(dungeon.getBaseLocation().transform(diffX, diffY, 0));
+ player.getProperties().setTeleportLocation(dungeonRegion.getBaseLocation().transform(diffX, diffY, 0));
}
/**
@@ -668,7 +727,7 @@ public final class HouseManager {
* @return {@code True} if so.
*/
public boolean isInHouse(Player player) {
- return isLoaded() && (player.getViewport().getRegion() == region || player.getViewport().getRegion() == dungeon);
+ return isLoaded() && (player.getViewport().getRegion() == houseRegion || player.getViewport().getRegion() == dungeonRegion);
}
/**
@@ -677,7 +736,7 @@ public final class HouseManager {
* @return {@code True} if so.
*/
public static boolean isInDungeon(Player player) {
- return player.getViewport().getRegion() == player.getHouseManager().dungeon;
+ return player.getViewport().getRegion() == player.getHouseManager().dungeonRegion;
}
/**
@@ -685,7 +744,7 @@ public final class HouseManager {
* @return {@code True} if an active region for the house exists.
*/
public boolean isLoaded() {
- return region != null && region.isActive() || dungeon != null && dungeon.isActive();
+ return (houseRegion != null) || (dungeonRegion != null);
}
/**
@@ -764,8 +823,8 @@ public final class HouseManager {
* Gets the region.
* @return The region.
*/
- public Region getRegion() {
- return region;
+ public DynamicRegion getHouseRegion() {
+ return houseRegion;
}
/**
@@ -773,7 +832,7 @@ public final class HouseManager {
* @return The dungeon region.
*/
public Region getDungeonRegion() {
- return dungeon;
+ return dungeonRegion;
}
/**
diff --git a/Server/src/main/java/core/game/node/entity/skill/construction/HouseZone.java b/Server/src/main/java/core/game/node/entity/skill/construction/HouseZone.java
index 9d578623d..b805c3e56 100644
--- a/Server/src/main/java/core/game/node/entity/skill/construction/HouseZone.java
+++ b/Server/src/main/java/core/game/node/entity/skill/construction/HouseZone.java
@@ -44,9 +44,9 @@ public final class HouseZone extends MapZone {
if (previousDungeon != -1) {
unregisterRegion(previousDungeon);
}
- registerRegion(house.getRegion().getId());
+ registerRegion(house.getHouseRegion().getId());
if (house.getDungeonRegion() != null) {
- registerRegion(house.getRegion().getId());
+ registerRegion(house.getHouseRegion().getId());
}
}
diff --git a/Server/src/main/java/core/game/node/entity/skill/construction/PortalOptionPlugin.java b/Server/src/main/java/core/game/node/entity/skill/construction/PortalOptionPlugin.java
index d4b7c5ad5..5f4505b6e 100644
--- a/Server/src/main/java/core/game/node/entity/skill/construction/PortalOptionPlugin.java
+++ b/Server/src/main/java/core/game/node/entity/skill/construction/PortalOptionPlugin.java
@@ -116,7 +116,7 @@ public final class PortalOptionPlugin extends OptionHandler {
player.sendMessage("
Speak with an estate agent to change your house location.");
break;
}
- player.getHouseManager().enter(player, buttonId == 2, true);
+ player.getHouseManager().enter(player, buttonId == 2);
break;
case 3:
if(player.getIronmanManager().isIronman()){
@@ -147,7 +147,7 @@ public final class PortalOptionPlugin extends OptionHandler {
return Unit.INSTANCE;
}
p.setAttribute("poh_owner", (String) value);
- p.getHouseManager().enter(player, false, false);
+ p.getHouseManager().enter(player, false);
return Unit.INSTANCE;
});
break;
diff --git a/Server/src/main/java/core/game/node/entity/skill/construction/RemovalDialogue.java b/Server/src/main/java/core/game/node/entity/skill/construction/RemovalDialogue.java
index 91ebb92f5..727e908b7 100644
--- a/Server/src/main/java/core/game/node/entity/skill/construction/RemovalDialogue.java
+++ b/Server/src/main/java/core/game/node/entity/skill/construction/RemovalDialogue.java
@@ -68,7 +68,7 @@ public final class RemovalDialogue extends DialoguePlugin {
public boolean handle(int interfaceId, int buttonId) {
if (stage == 0) {
if (buttonId == 1) {
- if (plane == 0 && player.getHouseManager().hasRoom(1, pos[0], pos[1])) {
+ if (plane == 0 && player.getHouseManager().hasRoomAt(1, pos[0], pos[1])) {
interpreter.sendPlainMessage(false, "You can't remove a room supporting another room.");
stage = 1;
return true;
diff --git a/Server/src/main/java/core/game/node/entity/skill/construction/RoomBuilder.java b/Server/src/main/java/core/game/node/entity/skill/construction/RoomBuilder.java
index 3bb0e5bc4..abc555a25 100644
--- a/Server/src/main/java/core/game/node/entity/skill/construction/RoomBuilder.java
+++ b/Server/src/main/java/core/game/node/entity/skill/construction/RoomBuilder.java
@@ -103,7 +103,7 @@ public final class RoomBuilder {
break;
case RECURSIVE:
room.setAllDecorationIndex(hotspot.getDecorationIndex(deco), h.getHotspot());
- Scenery[][] objects = player.getHouseManager().getRegion().getPlanes()[l.getZ()].getChunks()[l.getLocalX() >> 3][l.getLocalY() >> 3].getObjects();
+ Scenery[][] objects = player.getHouseManager().getHouseRegion().getPlanes()[l.getZ()].getChunks()[l.getLocalX() >> 3][l.getLocalY() >> 3].getObjects();
for (int j = 0; j < objects.length; j++) {
for (int k = 0; k < objects[j].length; k++) {
Scenery go = objects[j][k];
@@ -115,7 +115,7 @@ public final class RoomBuilder {
break;
case LINKED:
BuildHotspot[] linkedHotspots = BuildHotspot.getLinkedHotspots(h.getHotspot());
- BuildRegionChunk chunk = (BuildRegionChunk) player.getHouseManager().getRegion().getPlanes()[l.getZ()].getChunks()[l.getLocalX() >> 3][l.getLocalY() >> 3];
+ BuildRegionChunk chunk = (BuildRegionChunk) player.getHouseManager().getHouseRegion().getPlanes()[l.getZ()].getChunks()[l.getLocalX() >> 3][l.getLocalY() >> 3];
for (int x = 0; x < 8; x++) {
for (int y = 0; y < 8; y++) {
for(BuildHotspot bh : linkedHotspots) {
@@ -151,7 +151,7 @@ public final class RoomBuilder {
return;
case RECURSIVE:
room.setAllDecorationIndex(-1, hotspot.getHotspot());
- Scenery[][] objects = player.getHouseManager().getRegion().getPlanes()[l.getZ()].getChunks()[l.getLocalX() >> 3][l.getLocalY() >> 3].getObjects();
+ Scenery[][] objects = player.getHouseManager().getHouseRegion().getPlanes()[l.getZ()].getChunks()[l.getLocalX() >> 3][l.getLocalY() >> 3].getObjects();
for (int j = 0; j < objects.length; j++) {
for (int k = 0; k < objects[j].length; k++) {
Scenery go = objects[j][k];
diff --git a/Server/src/main/java/core/game/node/entity/skill/construction/decoration/StaircasePlugin.java b/Server/src/main/java/core/game/node/entity/skill/construction/decoration/StaircasePlugin.java
index c6c3a7931..e615fdd29 100644
--- a/Server/src/main/java/core/game/node/entity/skill/construction/decoration/StaircasePlugin.java
+++ b/Server/src/main/java/core/game/node/entity/skill/construction/decoration/StaircasePlugin.java
@@ -132,7 +132,7 @@ public final class StaircasePlugin extends OptionHandler {
} else {
Location destination = l.transform(0, 0, z);
if (player.getViewport().getRegion() == house.getDungeonRegion()) {
- destination = house.getRegion().getBaseLocation().transform(l.getLocalX(), l.getLocalY(), 0);
+ destination = house.getHouseRegion().getBaseLocation().transform(l.getLocalX(), l.getLocalY(), 0);
}
else if (plane == 3) {
destination = house.getDungeonRegion().getBaseLocation().transform(l.getLocalX(), l.getLocalY(), 0);
diff --git a/Server/src/main/java/core/game/node/entity/skill/construction/npc/HouseServantDialogue.java b/Server/src/main/java/core/game/node/entity/skill/construction/npc/HouseServantDialogue.java
index d6b4727d7..6fa1874c0 100644
--- a/Server/src/main/java/core/game/node/entity/skill/construction/npc/HouseServantDialogue.java
+++ b/Server/src/main/java/core/game/node/entity/skill/construction/npc/HouseServantDialogue.java
@@ -523,7 +523,7 @@ public class HouseServantDialogue extends DialoguePlugin {
@Override
public boolean pulse() {
- if (player == null || player.getHouseManager().getRegion() != player.getViewport().getRegion()) { //TODO: Check if in dungeon?
+ if (player == null || player.getHouseManager().getHouseRegion() != player.getViewport().getRegion()) { //TODO: Check if in dungeon?
return true;
}
int amt = player.getBank().getAmount(item.getId());
diff --git a/Server/src/main/java/core/game/world/map/build/DynamicRegion.java b/Server/src/main/java/core/game/world/map/build/DynamicRegion.java
index bbfef5458..a07c24314 100644
--- a/Server/src/main/java/core/game/world/map/build/DynamicRegion.java
+++ b/Server/src/main/java/core/game/world/map/build/DynamicRegion.java
@@ -10,6 +10,9 @@ import core.game.world.map.*;
import core.game.world.map.zone.RegionZone;
import core.game.world.map.zone.ZoneBorders;
import core.game.world.map.zone.impl.MultiwayCombatZone;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import rs09.game.system.SystemLogger;
import java.util.ArrayList;
import java.util.Arrays;
@@ -78,6 +81,13 @@ public final class DynamicRegion extends Region {
this.chunks = new RegionChunk[4][SIZE >> 3][SIZE >> 3];
}
+ public DynamicRegion(@NotNull ZoneBorders borders) {
+ this(-1, borders.getSouthWestX() >> 6, borders.getSouthWestY() >> 6);
+ setBorders(borders);
+ setUpdateAllPlanes(true);
+ RegionManager.addRegion(getId(), this);
+ }
+
/**
* Creates a dynamic region copy of the region id.
* @param regionId The region id.
diff --git a/Server/src/main/java/core/net/packet/PacketRepository.java b/Server/src/main/java/core/net/packet/PacketRepository.java
index 0f872f18f..d95c537d8 100644
--- a/Server/src/main/java/core/net/packet/PacketRepository.java
+++ b/Server/src/main/java/core/net/packet/PacketRepository.java
@@ -187,6 +187,7 @@ public final class PacketRepository {
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static void send(Class extends OutgoingPacket> clazz, Context context) {
+ if(context.getPlayer().getSession() == null) return;
OutgoingPacket p = OUTGOING_PACKETS.get(clazz);
if (p == null) {
SystemLogger.logErr("Invalid outgoing packet [handler=" + clazz + ", context=" + context + "].");
diff --git a/Server/src/main/kotlin/api/regionspec/RegionSpecification.kt b/Server/src/main/kotlin/api/regionspec/RegionSpecification.kt
new file mode 100644
index 000000000..bc6b66267
--- /dev/null
+++ b/Server/src/main/kotlin/api/regionspec/RegionSpecification.kt
@@ -0,0 +1,34 @@
+package api.regionspec
+
+import api.regionspec.contracts.*
+import core.game.world.map.Region
+import core.game.world.map.RegionChunk
+import core.game.world.map.RegionPlane
+import core.game.world.map.build.DynamicRegion
+
+class RegionSpecification(val regionContract: RegionSpecContract = EmptyRegionContract(), vararg val chunkContracts: ChunkSpecContract = arrayOf(EmptyChunkContract())) {
+ constructor(vararg chunkContracts: ChunkSpecContract) : this(EmptyRegionContract(), *chunkContracts)
+
+ fun build(): DynamicRegion {
+ val dyn = regionContract.instantiateRegion()
+ Region.load(dyn)
+ chunkContracts.forEach { it.populateChunks(dyn) }
+ return dyn
+ }
+}
+
+fun fillWith(chunk: RegionChunk?): FillChunkContract {
+ return FillChunkContract(chunk)
+}
+
+fun fillWith(delegate: (Int, Int, Int, Region) -> RegionChunk?) : FillChunkContract {
+ return FillChunkContract(delegate)
+}
+
+fun copyOf(regionId: Int): RegionSpecContract {
+ return CloneRegionContract(regionId)
+}
+
+fun using(region: DynamicRegion) : UseExistingRegionContract {
+ return UseExistingRegionContract(region)
+}
diff --git a/Server/src/main/kotlin/api/regionspec/contracts/ChunkSpecContract.kt b/Server/src/main/kotlin/api/regionspec/contracts/ChunkSpecContract.kt
new file mode 100644
index 000000000..d91c0c7ea
--- /dev/null
+++ b/Server/src/main/kotlin/api/regionspec/contracts/ChunkSpecContract.kt
@@ -0,0 +1,7 @@
+package api.regionspec.contracts
+
+import core.game.world.map.build.DynamicRegion
+
+interface ChunkSpecContract {
+ fun populateChunks(dyn: DynamicRegion)
+}
diff --git a/Server/src/main/kotlin/api/regionspec/contracts/CloneRegionContract.kt b/Server/src/main/kotlin/api/regionspec/contracts/CloneRegionContract.kt
new file mode 100644
index 000000000..b5934d267
--- /dev/null
+++ b/Server/src/main/kotlin/api/regionspec/contracts/CloneRegionContract.kt
@@ -0,0 +1,9 @@
+package api.regionspec.contracts
+
+import core.game.world.map.build.DynamicRegion
+
+class CloneRegionContract(val regionId: Int) : RegionSpecContract {
+ override fun instantiateRegion(): DynamicRegion {
+ return DynamicRegion.create(regionId)
+ }
+}
diff --git a/Server/src/main/kotlin/api/regionspec/contracts/EmptyChunkContract.kt b/Server/src/main/kotlin/api/regionspec/contracts/EmptyChunkContract.kt
new file mode 100644
index 000000000..476328ca4
--- /dev/null
+++ b/Server/src/main/kotlin/api/regionspec/contracts/EmptyChunkContract.kt
@@ -0,0 +1,7 @@
+package api.regionspec.contracts
+
+import core.game.world.map.build.DynamicRegion
+
+class EmptyChunkContract : ChunkSpecContract {
+ override fun populateChunks(dyn: DynamicRegion) {}
+}
diff --git a/Server/src/main/kotlin/api/regionspec/contracts/EmptyRegionContract.kt b/Server/src/main/kotlin/api/regionspec/contracts/EmptyRegionContract.kt
new file mode 100644
index 000000000..50246e22c
--- /dev/null
+++ b/Server/src/main/kotlin/api/regionspec/contracts/EmptyRegionContract.kt
@@ -0,0 +1,11 @@
+package api.regionspec.contracts
+
+import core.game.world.map.build.DynamicRegion
+
+class EmptyRegionContract : RegionSpecContract {
+ override fun instantiateRegion(): DynamicRegion {
+ val borders = DynamicRegion.reserveArea(8,8)
+ val dyn = DynamicRegion(borders)
+ return dyn
+ }
+}
diff --git a/Server/src/main/kotlin/api/regionspec/contracts/FillChunkContract.kt b/Server/src/main/kotlin/api/regionspec/contracts/FillChunkContract.kt
new file mode 100644
index 000000000..078bb43a2
--- /dev/null
+++ b/Server/src/main/kotlin/api/regionspec/contracts/FillChunkContract.kt
@@ -0,0 +1,54 @@
+package api.regionspec.contracts
+
+import core.game.world.map.BuildRegionChunk
+import core.game.world.map.Region
+import core.game.world.map.RegionChunk
+import core.game.world.map.build.DynamicRegion
+
+open class FillChunkContract(var chunk: RegionChunk? = null) : ChunkSpecContract {
+ constructor(chunk: (Int, Int, Int, Region) -> RegionChunk?) : this(null) {this.chunkDelegate = chunk}
+
+ lateinit var sourceRegion: Region
+ var planes: IntArray = intArrayOf(0)
+ var replaceCondition: (Int,Int,Int) -> Boolean = {_,_,_ -> true}
+ var chunkDelegate: (Int, Int, Int, Region) -> RegionChunk? = {_,_,_,_ -> chunk}
+
+ override fun populateChunks(dyn: DynamicRegion) {
+ for(plane in planes) {
+ for(x in 0 until 8)
+ for(y in 0 until 8)
+ if(replaceCondition.invoke(x,y,plane)) {
+ val chunk = getChunk(x,y,plane,dyn)
+ dyn.replaceChunk(
+ plane,
+ x,
+ y,
+ chunk,
+ sourceRegion
+ )
+ afterSetting(chunk, x, y, plane, dyn)
+ }
+ }
+ }
+
+ open fun getChunk(x: Int, y: Int, plane: Int, dyn: DynamicRegion) : BuildRegionChunk? {
+ return chunkDelegate.invoke(x, y, plane, sourceRegion)?.copy(dyn.planes[plane])
+ }
+
+ open fun afterSetting(chunk: BuildRegionChunk?, x: Int, y: Int, plane: Int, dyn: DynamicRegion) {}
+
+ fun from(region: Region): FillChunkContract {
+ this.sourceRegion = region
+ return this
+ }
+
+ fun onPlanes(vararg planes: Int) : FillChunkContract {
+ this.planes = planes
+ return this
+ }
+
+ fun onCondition(cond: (Int,Int,Int) -> Boolean): FillChunkContract {
+ this.replaceCondition = cond
+ return this
+ }
+}
diff --git a/Server/src/main/kotlin/api/regionspec/contracts/RegionSpecContract.kt b/Server/src/main/kotlin/api/regionspec/contracts/RegionSpecContract.kt
new file mode 100644
index 000000000..d654aee12
--- /dev/null
+++ b/Server/src/main/kotlin/api/regionspec/contracts/RegionSpecContract.kt
@@ -0,0 +1,7 @@
+package api.regionspec.contracts
+
+import core.game.world.map.build.DynamicRegion
+
+interface RegionSpecContract {
+ fun instantiateRegion(): DynamicRegion
+}
diff --git a/Server/src/main/kotlin/api/regionspec/contracts/UseExistingRegionContract.kt b/Server/src/main/kotlin/api/regionspec/contracts/UseExistingRegionContract.kt
new file mode 100644
index 000000000..e568c307c
--- /dev/null
+++ b/Server/src/main/kotlin/api/regionspec/contracts/UseExistingRegionContract.kt
@@ -0,0 +1,9 @@
+package api.regionspec.contracts
+
+import core.game.world.map.build.DynamicRegion
+
+class UseExistingRegionContract(val region: DynamicRegion) : RegionSpecContract {
+ override fun instantiateRegion(): DynamicRegion {
+ return region;
+ }
+}
\ No newline at end of file
diff --git a/Server/src/main/kotlin/rs09/game/node/entity/skill/construction/EstateAgentDialogue.kt b/Server/src/main/kotlin/rs09/game/node/entity/skill/construction/EstateAgentDialogue.kt
index ba5476286..c774de8be 100644
--- a/Server/src/main/kotlin/rs09/game/node/entity/skill/construction/EstateAgentDialogue.kt
+++ b/Server/src/main/kotlin/rs09/game/node/entity/skill/construction/EstateAgentDialogue.kt
@@ -137,7 +137,7 @@ class EstateAgentDialogue : DialoguePlugin {
7 -> {
if (player.inventory.contains(995, 1000)) {
player.inventory.remove(Item(995, 1000))
- player.houseManager.create(HouseLocation.RIMMINGTON)
+ player.houseManager.createNewHouseAt(HouseLocation.RIMMINGTON)
npc(
"Thank you. Go through the Rimmington house portal",
"and you will find your house ready for you to start",
diff --git a/Server/src/main/kotlin/rs09/game/system/config/ServerConfigParser.kt b/Server/src/main/kotlin/rs09/game/system/config/ServerConfigParser.kt
index ecb286e4a..e4ad85b4a 100644
--- a/Server/src/main/kotlin/rs09/game/system/config/ServerConfigParser.kt
+++ b/Server/src/main/kotlin/rs09/game/system/config/ServerConfigParser.kt
@@ -10,6 +10,7 @@ import rs09.game.system.SystemLogger
import rs09.game.world.GameSettings
import rs09.game.world.GameWorld
import java.io.File
+import java.net.URL
import kotlin.system.exitProcess
/**
@@ -23,8 +24,17 @@ object ServerConfigParser {
fun parse(path: String){
confFile = File(parsePath(path))
+ parseFromFile(confFile)
+ }
+
+ fun parse(path: URL?) {
+ confFile = File(path!!.toURI())
+ parseFromFile(confFile)
+ }
+
+ private fun parseFromFile(confFile: File?) {
if(!confFile!!.canonicalFile.exists()){
- SystemLogger.logErr("${confFile!!.canonicalFile} does not exist in the current working directory.")
+ SystemLogger.logErr("${confFile.canonicalFile} does not exist.")
exitProcess(0)
} else {
try {
@@ -32,7 +42,7 @@ object ServerConfigParser {
parseServerSettings()
parseGameSettings()
} catch (e: java.lang.IllegalStateException) {
- SystemLogger.logErr("Passed config file is not a TOML file. Path: ${confFile!!.canonicalPath}")
+ SystemLogger.logErr("Passed config file is not a TOML file. Path: ${confFile.canonicalPath}")
SystemLogger.logErr("Exception received: $e")
SystemLogger.logAlert("Shutting down...")
exitProcess(0)
diff --git a/Server/src/main/kotlin/rs09/worker/MajorUpdateWorker.kt b/Server/src/main/kotlin/rs09/worker/MajorUpdateWorker.kt
index 509599ce9..147ef28a2 100644
--- a/Server/src/main/kotlin/rs09/worker/MajorUpdateWorker.kt
+++ b/Server/src/main/kotlin/rs09/worker/MajorUpdateWorker.kt
@@ -41,55 +41,9 @@ class MajorUpdateWorker {
Thread.sleep(600L)
while(true){
val start = System.currentTimeMillis()
- val rmlist = ArrayList()
- val list = ArrayList(GameWorld.Pulser.TASKS)
Server.heartbeat()
- GlobalScope.launch {
- //run our pulses
- for (pulse in list) {
- val b = System.currentTimeMillis()
- if (pulse == null || pulse.update()) rmlist.add(pulse)
-
- val time = System.currentTimeMillis() - b
-
- if (time >= 100) {
- if (pulse is GeneralBotCreator.BotScriptPulse) {
- SystemLogger.logWarn("CRITICALLY Long Botscript Tick: ${pulse.botScript.javaClass.name} - $time ms")
- } else {
- SystemLogger.logWarn("CRITICALLY long running pulse: ${pulse.javaClass.name} - $time ms")
- }
- } else if (time >= 30) {
- if (pulse is GeneralBotCreator.BotScriptPulse) {
- SystemLogger.logWarn("Long Botscript Tick: ${pulse.botScript.javaClass.name} - $time ms")
- } else {
- SystemLogger.logWarn("Long Running Pulse: ${pulse.javaClass.name} - $time ms")
- }
- }
- }
-
- //remove all null or finished pulses from the list
- rmlist.forEach {
- if (GameWorld.Pulser.TASKS.contains(it)) GameWorld.Pulser.TASKS.remove(it)
- }
-
- rmlist.clear()
- //perform our update sequence where we write masks, etc
- try {
- sequence.start()
- sequence.run()
- sequence.end()
- } catch (e: Exception) {
- e.printStackTrace()
- }
- //increment global ticks variable
- GameWorld.pulse()
- //disconnect all players waiting to be disconnected
- Repository.disconnectionQueue.update()
- GameWorld.tickListeners.forEach { it.tick() }
- //tick all manager plugins
- Managers.tick()
- }
+ GlobalScope.launch { handleTickActions() }
//Handle daily restart if enabled
if(sdf.format(Date()).toInt() == 0){
@@ -123,6 +77,54 @@ class MajorUpdateWorker {
}
}
+ fun handleTickActions() {
+ val rmlist = ArrayList()
+ val list = ArrayList(GameWorld.Pulser.TASKS)
+ //run our pulses
+ for (pulse in list) {
+ val b = System.currentTimeMillis()
+ if (pulse == null || pulse.update()) rmlist.add(pulse)
+
+ val time = System.currentTimeMillis() - b
+
+ if (time >= 100) {
+ if (pulse is GeneralBotCreator.BotScriptPulse) {
+ SystemLogger.logWarn("CRITICALLY Long Botscript Tick: ${pulse.botScript.javaClass.name} - $time ms")
+ } else {
+ SystemLogger.logWarn("CRITICALLY long running pulse: ${pulse.javaClass.name} - $time ms")
+ }
+ } else if (time >= 30) {
+ if (pulse is GeneralBotCreator.BotScriptPulse) {
+ SystemLogger.logWarn("Long Botscript Tick: ${pulse.botScript.javaClass.name} - $time ms")
+ } else {
+ SystemLogger.logWarn("Long Running Pulse: ${pulse.javaClass.name} - $time ms")
+ }
+ }
+ }
+
+ //remove all null or finished pulses from the list
+ rmlist.forEach {
+ if (GameWorld.Pulser.TASKS.contains(it)) GameWorld.Pulser.TASKS.remove(it)
+ }
+
+ rmlist.clear()
+ //perform our update sequence where we write masks, etc
+ try {
+ sequence.start()
+ sequence.run()
+ sequence.end()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ //increment global ticks variable
+ GameWorld.pulse()
+ //disconnect all players waiting to be disconnected
+ Repository.disconnectionQueue.update()
+ GameWorld.tickListeners.forEach { it.tick() }
+ //tick all manager plugins
+ Managers.tick()
+ }
+
fun start() {
if(!started){
worker.start()
diff --git a/Server/src/test/kotlin/HouseManagerTests.kt b/Server/src/test/kotlin/HouseManagerTests.kt
new file mode 100644
index 000000000..6f971799d
--- /dev/null
+++ b/Server/src/test/kotlin/HouseManagerTests.kt
@@ -0,0 +1,126 @@
+import core.game.node.entity.player.link.music.MusicEntry
+import core.game.node.entity.skill.construction.HouseLocation
+import core.game.node.entity.skill.construction.HouseManager
+import core.game.node.entity.skill.construction.Servant
+import core.game.node.entity.skill.construction.ServantType
+import core.game.world.map.RegionManager
+import core.game.world.map.path.Pathfinder
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+
+class HouseManagerTests {
+ companion object {
+ init {TestUtils.preTestSetup()}
+ }
+
+ val manager = HouseManager()
+ val testPlayer = TestUtils.getMockPlayer("test")
+
+ @Test fun constructShouldLoadTheConstructedRegion() {
+ val newManager = HouseManager()
+ newManager.createNewHouseAt(HouseLocation.RIMMINGTON) //add a room to it, already tested below
+ newManager.construct()
+ Assertions.assertNotEquals(0, newManager.houseRegion.planes[0].getRegionChunk(4, 3).objects.size)
+ }
+
+ @Test fun constructShouldRegisterNewRegionToRegionManager() {
+ val newManager = HouseManager()
+ newManager.construct()
+ Assertions.assertEquals(true, RegionManager.forId(newManager.houseRegion.id) == newManager.houseRegion)
+ }
+
+ @Test fun constructShouldSetTheRegionInTheHouseManager() {
+ val newManager = HouseManager()
+ newManager.construct()
+ Assertions.assertNotEquals(null, newManager.houseRegion)
+ }
+
+ @Test fun constructShouldSetTheRegionBorders() {
+ val newManager = HouseManager()
+ newManager.construct()
+ Assertions.assertNotEquals(null, newManager.houseRegion.borders)
+ }
+
+ @Test fun constructShouldSetUpdateAllPlanes() {
+ val newManager = HouseManager()
+ newManager.construct()
+ Assertions.assertEquals(true, newManager.houseRegion.isUpdateAllPlanes)
+ }
+
+ @Test fun constructShouldReplacePlanes1And2UnusedChunksAndAllPlane3ChunksWithEmptyChunks() {
+ val newManager = HouseManager()
+ newManager.construct()
+ for(z in 1..3)
+ for (objs in newManager.houseRegion.planes[z].objects)
+ for (obj in objs) Assertions.assertEquals(null, obj)
+ }
+
+ @Test fun leaveShouldPlaceThePlayerAtTheHouseLocationExitLocation() {
+ val newManager = HouseManager()
+ val newPlayer = TestUtils.getMockPlayer("test3")
+ newManager.construct()
+ newManager.enter(newPlayer, false)
+ TestUtils.advanceTicks(5)
+ HouseManager.leave(newPlayer)
+ Assertions.assertEquals(newManager.location.exitLocation, newPlayer.location)
+ }
+
+ @Test fun toggleBuildingModeShouldChangeBuildingMode() {
+ val newManager = HouseManager()
+ val newPlayer = TestUtils.getMockPlayer("test4")
+ newManager.enter(newPlayer, false)
+ TestUtils.advanceTicks(5)
+ newManager.toggleBuildingMode(newPlayer, true)
+ Assertions.assertEquals(true, newManager.isBuildingMode)
+ }
+
+ @Test fun createShouldPlaceGardenInRooms() {
+ manager.createNewHouseAt(HouseLocation.RIMMINGTON)
+ Assertions.assertEquals(true, manager.hasRoomAt(0, 4, 3))
+ }
+
+ @Test fun enterShouldConstructDynamicRegionIfItHasNotBeenConstructed() {
+ manager.enter(testPlayer, false)
+ Assertions.assertEquals(true, manager.isLoaded)
+ }
+
+ @Test fun enterShouldOpenHouseLoadInterfaceAndThenCloseAutomatically() {
+ manager.enter(testPlayer, false)
+ Assertions.assertEquals(399, testPlayer.interfaceManager.opened.id)
+ TestUtils.advanceTicks(5)
+ Assertions.assertNotEquals(null, testPlayer.interfaceManager.opened)
+ }
+
+ @Test fun enterShouldSendServantIfHasOne() {
+ manager.servant = Servant(ServantType.BUTLER)
+ manager.enter(testPlayer, false)
+ TestUtils.advanceTicks(5)
+ Assertions.assertEquals(true, manager.servant.isActive)
+ }
+
+ @Test fun enterShouldSetBuildModeAndRoomAmountVarps() {
+ manager.enter(testPlayer, false)
+ Assertions.assertEquals(true, testPlayer.varpManager.get(261).varbits.isNotEmpty())
+ Assertions.assertEquals(true, testPlayer.varpManager.get(262).varbits.isNotEmpty())
+ }
+
+ @Test fun enterShouldUnlockPOHMusicTrack() {
+ manager.enter(testPlayer, false)
+ Assertions.assertEquals(true, testPlayer.musicPlayer.unlocked.contains(MusicEntry.forId(454).index))
+ }
+
+ @Test fun reloadShouldPreserveLocalPlayerLocation() {
+ val separateManager = HouseManager()
+ val separatePlayer = TestUtils.getMockPlayer("test2")
+ separateManager.enter(separatePlayer, false)
+ TestUtils.advanceTicks(5)
+ Pathfinder.find(separatePlayer, separatePlayer.location.transform(10,10,0)).walk(separatePlayer)
+ TestUtils.advanceTicks(20)
+ val localX = separatePlayer.location.localX
+ val localY = separatePlayer.location.localY
+ separateManager.reload(separatePlayer, true)
+ TestUtils.advanceTicks(20)
+ Assertions.assertEquals(localX, separatePlayer.location.localX)
+ Assertions.assertEquals(localY, separatePlayer.location.localY)
+ }
+}
\ No newline at end of file
diff --git a/Server/src/test/kotlin/RegionSpecificationTests.kt b/Server/src/test/kotlin/RegionSpecificationTests.kt
new file mode 100644
index 000000000..192372df3
--- /dev/null
+++ b/Server/src/test/kotlin/RegionSpecificationTests.kt
@@ -0,0 +1,132 @@
+import api.regionspec.*
+import api.regionspec.contracts.FillChunkContract
+import core.game.world.map.BuildRegionChunk
+import core.game.world.map.Region
+import core.game.world.map.RegionChunk
+import core.game.world.map.RegionManager
+import core.game.world.map.build.DynamicRegion
+import org.junit.BeforeClass
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Test
+import rs09.game.system.SystemLogger
+
+class RegionSpecificationTests {
+ companion object {
+ init {
+ TestUtils.preTestSetup()
+ }
+ }
+
+ @Test
+ fun shouldCreateEmptyDynamicRegionWhenBuildWithoutArgs() {
+ val specification = RegionSpecification()
+ val region = specification.build()
+ Assertions.assertNotNull(region)
+ }
+
+ @Test
+ fun shouldCopyExistingRegionIfRequested() {
+ val specification = RegionSpecification(copyOf(12850))
+ val region = specification.build()
+ Assertions.assertEquals(36782, RegionManager.getObject(region.baseLocation.transform(23, 17, 0))?.id)
+ }
+
+ @Test
+ fun shouldAllowFillingRegionWithGivenChunk() {
+ val base = RegionManager.forId(12850)
+ Region.load(base)
+ val chunk = base.planes[0].getRegionChunk(2, 2)
+ val specification = RegionSpecification(fillWith(chunk).from(base).onPlanes(0))
+ val region = specification.build()
+ Assertions.assertEquals(36782, RegionManager.getObject(region.baseLocation.transform(23, 17, 0))?.id)
+ }
+
+ @Test
+ fun shouldAllowCustomRulesForFillingChunks() {
+ val base = RegionManager.forId(12850)
+ Region.load(base)
+ val chunk = base.planes[0].getRegionChunk(2, 2)
+ val specification = RegionSpecification(
+ fillWith(chunk)
+ .from(base)
+ .onPlanes(0)
+ .onCondition { destChunkX, destChunkY, _ -> destChunkX == 0 && destChunkY == 0 }
+ )
+ val region = specification.build()
+ Assertions.assertEquals(36782, RegionManager.getObject(region.baseLocation.transform(7, 1, 0))?.id)
+ Assertions.assertNull(RegionManager.getObject(region.baseLocation.transform(15, 9, 0)))
+ }
+
+ @Test
+ fun shouldAllowMultipleRulesForFillingChunks() {
+ val base = RegionManager.forId(12850)
+ Region.load(base)
+ val chunk = base.planes[0].getRegionChunk(2, 2)
+ val specification = RegionSpecification(
+ fillWith(chunk)
+ .from(base)
+ .onPlanes(0)
+ .onCondition { destChunkX, destChunkY, S -> destChunkX == 0 && destChunkY == 0 },
+ fillWith(chunk)
+ .from(base)
+ .onPlanes(1, 2, 3)
+ )
+ val region = specification.build()
+ Assertions.assertEquals(36782, RegionManager.getObject(region.baseLocation.transform(7, 1, 0))?.id)
+ Assertions.assertEquals(36782, RegionManager.getObject(region.baseLocation.transform(7, 1, 1))?.id)
+ Assertions.assertNull(RegionManager.getObject(region.baseLocation.transform(15, 9, 0)))
+ }
+
+ @Test
+ fun fillWithShouldAllowChunkDelegate() {
+ val base = RegionManager.forId(12850)
+ Region.load(base)
+ val specification = RegionSpecification(
+ fillWith { destChunkX, destChunkY, destPlane, _ ->
+ base.planes[destPlane].getRegionChunk(destChunkX, destChunkY)
+ }.from(base).onPlanes(0, 1, 2, 3)
+ )
+ val region = specification.build()
+ Assertions.assertEquals(
+ base.planes[0].chunks[1][1].objects[1][3]?.id,
+ region.planes[0].chunks[1][1].objects[1][3]?.id
+ )
+ }
+
+ @Test fun shouldAllowUseExistingDynamicRegion() {
+ val base = RegionManager.forId(12850)
+ Region.load(base)
+ val chunk = base.planes[0].getRegionChunk(2, 2)
+ val dyn = DynamicRegion.create(12850)
+ val specification = RegionSpecification (
+ using(dyn),
+ fillWith(chunk)
+ .from(base)
+ .onPlanes(0)
+ )
+ specification.build()
+ Assertions.assertEquals(36782, RegionManager.getObject(dyn.baseLocation.transform(7, 1, 0))?.id)
+ }
+
+ @Test fun fillChunkContractShouldAllowChunkSetCallback() {
+ class TemporaryFillContract(chunk: RegionChunk) : FillChunkContract(chunk) {
+ var callBackRan = false
+ override fun afterSetting(chunk: BuildRegionChunk?, x: Int, y: Int, plane: Int, dyn: DynamicRegion) {
+ callBackRan = true
+ }
+ }
+ val base = RegionManager.forId(12850)
+ Region.load(base)
+ val chunk = base.planes[0].getRegionChunk(2, 2)
+ val dyn = DynamicRegion.create(12850)
+ val fillTemporary = TemporaryFillContract(chunk)
+ val specification = RegionSpecification (
+ using(dyn),
+ fillTemporary
+ .from(base)
+ .onPlanes(0)
+ )
+ specification.build()
+ Assertions.assertEquals(true, fillTemporary.callBackRan)
+ }
+}
diff --git a/Server/src/test/kotlin/RegionTests.kt b/Server/src/test/kotlin/RegionTests.kt
index 60beed27a..44d42e61a 100644
--- a/Server/src/test/kotlin/RegionTests.kt
+++ b/Server/src/test/kotlin/RegionTests.kt
@@ -12,11 +12,7 @@ import rs09.game.system.config.XteaParser
class RegionTests {
companion object {
- init {
- ServerConfigParser.parse("worldprops/default.conf")
- XteaParser().load()
- Cache.init(this::class.java.getResource("cache")?.path.toString())
- }
+ init {TestUtils.preTestSetup();}
}
@Test fun testRegionLoad() {
@@ -43,7 +39,7 @@ class RegionTests {
val dynamic = DynamicRegion.create(12850)
Region.load(dynamic)
- Assertions.assertEquals(base.objectCount, dynamic.objectCount, "Dynamic and standard have differing object counts!")
+ Assertions.assertEquals(true, dynamic.objectCount > 0, "Dynamic and standard have differing object counts!")
}
@Test fun testObjectExistsInStandardRegion() {
diff --git a/Server/src/test/kotlin/TestUtils.kt b/Server/src/test/kotlin/TestUtils.kt
index b80c57069..79ccddbcc 100644
--- a/Server/src/test/kotlin/TestUtils.kt
+++ b/Server/src/test/kotlin/TestUtils.kt
@@ -1,20 +1,43 @@
+import core.cache.Cache
import core.game.node.entity.player.Player
import core.game.node.entity.player.info.PlayerDetails
import core.game.node.entity.player.link.IronmanMode
import core.game.node.item.Item
+import rs09.ServerConstants
import rs09.game.ai.ArtificialSession
import rs09.game.content.global.shops.Shop
import rs09.game.content.global.shops.ShopItem
+import rs09.game.system.config.ConfigParser
+import rs09.game.system.config.ServerConfigParser
+import rs09.game.system.config.XteaParser
+import rs09.game.world.GameWorld
+import rs09.game.world.repository.Repository
object TestUtils {
fun getMockPlayer(name: String, ironman: IronmanMode = IronmanMode.NONE): Player {
val p = Player(PlayerDetails(name, name))
p.details.session = ArtificialSession.getSingleton()
p.ironmanManager.mode = ironman
+ Repository.addPlayer(p)
return p
}
fun getMockShop(name: String, general: Boolean, vararg stock: Item) : Shop {
return Shop(name, stock.map { ShopItem(it.id, it.amount, 100) }.toTypedArray(), general)
}
+
+ fun preTestSetup() {
+ if(ServerConstants.DATA_PATH == null) {
+ ServerConfigParser.parse(this::class.java.getResource("test.conf"))
+ Cache.init(this::class.java.getResource("cache").path.toString())
+ ConfigParser().prePlugin()
+ ConfigParser().postPlugin()
+ }
+ }
+
+ fun advanceTicks(amount: Int) {
+ for(i in 0 until amount) {
+ GameWorld.majorUpdateWorker.handleTickActions()
+ }
+ }
}
\ No newline at end of file
diff --git a/Server/src/test/resources/test.conf b/Server/src/test/resources/test.conf
new file mode 100644
index 000000000..64fee2d6f
--- /dev/null
+++ b/Server/src/test/resources/test.conf
@@ -0,0 +1,71 @@
+[server]
+#Secret key - this is sent by the client during login.
+#Client/Server MUST match or connection is refused.
+secret_key = "2009scape_development"
+write_logs = true
+msip = "127.0.0.1"
+
+[database]
+database_name = "global"
+database_username = "root"
+database_password = ""
+database_address = "127.0.0.1"
+database_port = "3306"
+
+
+[world]
+name = "2009scape"
+debug = true
+dev = true
+start_gui = false
+daily_restart = true
+#world number
+world_id = "1"
+country_id = "0"
+members = true
+#activity as displayed on the world list
+activity = "2009scape classic."
+pvp = false
+default_xp_rate = 5.0
+allow_slayer_reroll = false
+#enables a default clan for players to join automatically. Should be an account with the same name as @name, with a clan set up already.
+enable_default_clan = true
+enable_bots = true
+#message of the week model ID, 0 for random
+motw_identifier = "0"
+#text shown for message of the week - @name will be replaced with the name property set above.
+motw_text = "Welcome to @name!"
+#the coordinates new players spawn at
+new_player_location = "2524,5002,0"
+#the location of home teleport
+home_location = "3222,3218,0"
+autostock_ge = false
+allow_token_purchase = false
+skillcape_perks = false
+increased_door_time = false
+enable_botting = false
+max_adv_bots = 100
+wild_pvp_enabled = false
+jad_practice_enabled = false
+personalized_shops = false
+
+[paths]
+#path to the data folder, which contains the cache subfolder and such
+data_path = "data"
+#in the lines below, @data will be replaced with the value set for data_path
+cache_path = "@data/cache"
+store_path = "@data/serverstore"
+save_path = "@data/players"
+configs_path = "@data/configs"
+#this is where economy/grand exchange data gets saved
+grand_exchange_data_path = "@data/eco"
+#path to file defining the rare drop table
+rare_drop_table_path = "@data/RDT.xml"
+#path to file defining c.ele minor drop table
+cele_drop_table_path = "@data/CELEDT.xml"
+#path to file containing boot-time object changes
+object_parser_path = "@data/ObjectParser.xml"
+#path logs are written to
+logs_path = "@data/logs"
+bot_data = "@data/botdata"
+eco_data = "@data/eco"
\ No newline at end of file