Skip to content

Commit

Permalink
Dynamic instanced maps first draft rough code. Supports creating inst…
Browse files Browse the repository at this point in the history
…ances, pooled empty space for instances, etc.
  • Loading branch information
lare96 committed Dec 31, 2024
1 parent 708a428 commit a644d19
Show file tree
Hide file tree
Showing 7 changed files with 864 additions and 0 deletions.
266 changes: 266 additions & 0 deletions src/main/java/io/luna/game/model/map/DynamicMap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
package io.luna.game.model.map;

import io.luna.LunaContext;
import io.luna.game.model.Entity;
import io.luna.game.model.EntityType;
import io.luna.game.model.Position;
import io.luna.game.model.World;
import io.luna.game.model.chunk.Chunk;
import io.luna.game.model.chunk.ChunkRepository;
import io.luna.game.model.item.GroundItem;
import io.luna.game.model.map.builder.DynamicMapBuilder;
import io.luna.game.model.map.builder.DynamicMapChunk;
import io.luna.game.model.map.builder.DynamicMapPalette;
import io.luna.game.model.mob.Npc;
import io.luna.game.model.mob.Player;
import io.luna.game.model.mob.controller.ControllerKey;
import io.luna.game.model.object.GameObject;
import io.luna.net.msg.out.DynamicRegionMessageWriter;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
* A dynamically constructed map; also known as an instance, somewhere in the rs2 world. These can be used for things
* like minigames, private areas, random events, and player cutscenes.
* <p>
* Dynamic maps can be constructed simply by using specialized functions within {@link DynamicMapBuilder}, while
* {@link DynamicMapPalette} allows for finer control over chunk placement in the palette.
* <p>
* All instances are assigned empty space somewhere in the world, which is reclaimed when the instance is no longer
* needed. This ensures that instances are always isolated and that empty space is always available.
*
* @author lare96
*/
public final class DynamicMap {

/**
* The world instance.
*/
private final World world;

/**
* The base real chunk that will be used to translate coordinates from the real map to this instanced map.
*/
private final Chunk baseChunk;

/**
* The palette of this map.
*/
private final DynamicMapPalette palette;

/**
* The key of the controller for this instance.
*/
private final ControllerKey<?> controllerKey;

/**
* The region update message that will be sent.
*/
private final DynamicRegionMessageWriter regionUpdateMsg;

/**
* The players within this instance.
*/
private final Set<Player> players = new LinkedHashSet<>();

/**
* The empty space that this instance is assigned to.
*/
private DynamicMapSpace assignedSpace;

/**
* Creates a new {@link DynamicMap}.
*/
public DynamicMap(LunaContext context, Chunk baseChunk,
DynamicMapPalette palette, ControllerKey<? extends DynamicMapController> controllerKey,
DynamicRegionMessageWriter regionUpdateMsg) {
this.baseChunk = baseChunk;
this.palette = palette;
this.controllerKey = controllerKey;
this.regionUpdateMsg = regionUpdateMsg;
world = context.getWorld();
}

/**
* Creates this instance by requesting empty space, and transferring all map data over to the empty space.
*/
public void create() {
// Request empty space from the pool.
assignedSpace = world.getDynamicMapSpacePool().request();

// Get the base chunks from the palette.
Set<Chunk> realChunks = new HashSet<>();
palette.forEach((palette, x, y, z) -> {
DynamicMapChunk mapChunk = palette.getChunk(x, y, z);
if(mapChunk != null) {
realChunks.add(mapChunk.getChunk());
}
});

// Now add the static objects in the real map to our instanced map.
for (Chunk chunk : realChunks) {
ChunkRepository repository = world.getChunks().load(chunk);
repository.getAll(EntityType.OBJECT).stream().map(it -> (GameObject) it).
filter(it -> !it.isDynamic()).forEach(it -> {
GameObject instanceObject = GameObject.createStatic(world.getContext(), it.getId(),
getInstancePosition(it.getPosition()), it.getObjectType(), it.getDirection());
world.getObjects().register(instanceObject);
});
}
}

/**
* Attempts to add {@code plr} to this instance.
*
* @param plr The player to add.
*/
public boolean join(Player plr) {
if (players.add(plr)) {
plr.setDynamicMap(this);
plr.getControllers().register(controllerKey);
plr.queue(regionUpdateMsg);
return true;
}
return false;
}

/**
* Attempts to remove {@code plr} from this instance.
*
* @param plr The player to remove.
*/
public boolean leave(Player plr) {
if (players.remove(plr)) {
Position oldPosition = plr.getPosition();
plr.setLastRegion(null);
plr.getControllers().unregister(controllerKey);
plr.setDynamicMap(null);
plr.sendRegionUpdate(oldPosition);
return true;
}
return false;
}

/**
* Deletes this instance by forcing all players to leave, and clearing the area of entities.
*/
public void delete() {
// Force all players to leave.
Set<Player> removePlayers = new HashSet<>(players);
for (Player player : removePlayers) {
leave(player);
}

// Clear all entities.
int mainRegionId = assignedSpace.getMain().getId();
int paddingRegionId = assignedSpace.getPadding().getId();

Set<Chunk> clearChunks = new HashSet<>();
clearChunks.addAll(DynamicMapPalette.getAllChunksInRegion(mainRegionId));
clearChunks.addAll(DynamicMapPalette.getAllChunksInRegion(paddingRegionId));

List<Runnable> removalActions = new ArrayList<>();
for (Chunk chunk : clearChunks) {
ChunkRepository repository = world.getChunks().load(chunk);
for (var next : repository.getAll().entrySet()) {
EntityType type = next.getKey();
Set<Entity> entities = next.getValue();
for (Entity entity : entities) {
switch (type) {
case ITEM:
removalActions.add(() -> world.getItems().unregister((GroundItem) entity));
break;
case NPC:
removalActions.add(() -> world.getNpcs().remove((Npc) entity));
break;
case OBJECT:
removalActions.add(() -> world.getObjects().unregister((GameObject) entity));
break;
}
}
}
removalActions.add(repository::clear);
}

// Finalize removals.
for (Runnable action : removalActions) {
action.run();
}

// Return empty space back to pool.
world.getDynamicMapSpacePool().release(this);
}

/**
* Retrieves the {@link Position} in this instance that mirrors the actual coordinate.
*
* @param actualPosition The real position to get the instance position of.
* @return The instance position.
*/
public Position getInstancePosition(Position actualPosition) {
// Determine the deltas between a real arbitrary base position and the base display chunk. We can use
// these deltas later to translate from our instance position the exact same way.
Position basePosition = baseChunk.getAbsPosition();
int deltaX = actualPosition.getX() - basePosition.getX();
int deltaY = actualPosition.getY() - basePosition.getY();

// The base position in our instance will always be the same spot as base display chunk. Therefore we can do a
// 1:1 translation using the previous deltas.
return assignedSpace.getMain().getBasePosition().translate(deltaX, deltaY);
}

/**
* Forwards to {@link DynamicMap#getInstancePosition(Position)} with a {@code z} value of 0.
*
* @param x The real {@code x}.
* @param y The real {@code y}.
* @return The instance position.
*/
public Position getInstancePosition(int x, int y) {
return getInstancePosition(x, y, 0);
}

/**
* Forwards to {@link DynamicMap#getInstancePosition(Position)}.
*
* @param x The real {@code x}.
* @param y The real {@code y}.
* @param z The real {@code z}.
* @return The instance position.
*/
public Position getInstancePosition(int x, int y, int z) {
return getInstancePosition(new Position(x, y, z));
}

/**
* @return The empty space that this instance is assigned to.
*/
public DynamicMapSpace getAssignedSpace() {
return assignedSpace;
}

/**
* @return The base real chunk that will be used to translate coordinates from the real map to this instanced map.
*/
public Chunk getBaseChunk() {
return baseChunk;
}

/**
* @return The palette of this map.
*/
public DynamicMapPalette getPalette() {
return palette;
}

/**
* @return The key of the controller for this instance.
*/
public ControllerKey<?> getControllerKey() {
return controllerKey;
}
}
23 changes: 23 additions & 0 deletions src/main/java/io/luna/game/model/map/DynamicMapController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.luna.game.model.map;

import io.luna.game.model.Position;
import io.luna.game.model.mob.Player;
import io.luna.game.model.mob.controller.PlayerController;

public abstract class DynamicMapController extends PlayerController {

// TODO temporary? need to think more about what could be added here
@Override
public final void onRegister(Player player) {
player.teleport(enter(player));
}

@Override
public final void onUnregister(Player player) {
player.teleport(exit(player));
}

public abstract Position enter(Player player);

public abstract Position exit(Player player);
}
51 changes: 51 additions & 0 deletions src/main/java/io/luna/game/model/map/DynamicMapSpace.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.luna.game.model.map;

import com.google.common.base.Objects;
import io.luna.game.model.Location;
import io.luna.game.model.Position;
import io.luna.game.model.Region;

public class DynamicMapSpace implements Location {

private final Region main;
private final Region padding;

public DynamicMapSpace(Region main) {
this.main = main;
padding = new Region(main.getX() + 1, main.getY());
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DynamicMapSpace)) return false;
DynamicMapSpace space = (DynamicMapSpace) o;
return Objects.equal(main, space.main) && Objects.equal(padding, space.padding);
}

@Override
public int hashCode() {
return Objects.hashCode(main, padding);
}

@Override
public boolean contains(Position position) {
return main.contains(position) || padding.contains(position);
}

public boolean isVisibleTo(DynamicMapSpace other) {
return main.isWithinDistance(other.main, 1) ||
main.isWithinDistance(other.padding, 1) ||
padding.isWithinDistance(other.main, 1) ||
padding.isWithinDistance(other.padding, 1);
}

public Region getMain() {
return main;
}

public Region getPadding() {
return padding;
}

}
Loading

0 comments on commit a644d19

Please sign in to comment.