mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-06 00:38:18 +00:00
Add NpcMovementTask which randomly moves bounded NPCs around the map, fix Npc#equals, bug fixes for Pathfinding and CollisionMatrix.
This commit is contained in:
@@ -33,11 +33,12 @@ end
|
|||||||
|
|
||||||
# Spawns the specified npc and applies the properties in the hash.
|
# Spawns the specified npc and applies the properties in the hash.
|
||||||
def spawn(npc, hash)
|
def spawn(npc, hash)
|
||||||
$world.register(npc)
|
|
||||||
unless hash.empty?
|
unless hash.empty?
|
||||||
hash = decode_hash(npc.position, hash) # Use npc.position here because sector registry events (called by World.register) can be hooked
|
hash = decode_hash(npc.position, hash)
|
||||||
apply_decoded_hash(npc, hash) # into and someone might do something daft like move the npc immediately after it gets spawned.
|
apply_decoded_hash(npc, hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
$world.register(npc)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns an npc with the id and position specified by the hash.
|
# Returns an npc with the id and position specified by the hash.
|
||||||
@@ -54,7 +55,7 @@ def apply_decoded_hash(npc, hash)
|
|||||||
hash.each do |key, value|
|
hash.each do |key, value|
|
||||||
case key
|
case key
|
||||||
when :face then npc.turn_to(value)
|
when :face then npc.turn_to(value)
|
||||||
when :boundary then npc.boundary = value
|
when :boundary then npc.boundaries = value
|
||||||
when :spawn_animation then npc.play_animation(Animation.new(value))
|
when :spawn_animation then npc.play_animation(Animation.new(value))
|
||||||
when :spawn_graphic then npc.play_graphic(Graphic.new(value))
|
when :spawn_graphic then npc.play_graphic(Graphic.new(value))
|
||||||
else raise "Unrecognised key #{key} - value #{value}."
|
else raise "Unrecognised key #{key} - value #{value}."
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import org.apollo.game.model.event.EventListener;
|
|||||||
import org.apollo.game.model.event.EventListenerChainSet;
|
import org.apollo.game.model.event.EventListenerChainSet;
|
||||||
import org.apollo.game.scheduling.ScheduledTask;
|
import org.apollo.game.scheduling.ScheduledTask;
|
||||||
import org.apollo.game.scheduling.Scheduler;
|
import org.apollo.game.scheduling.Scheduler;
|
||||||
|
import org.apollo.game.scheduling.impl.NpcMovementTask;
|
||||||
import org.apollo.io.EquipmentDefinitionParser;
|
import org.apollo.io.EquipmentDefinitionParser;
|
||||||
import org.apollo.util.MobRepository;
|
import org.apollo.util.MobRepository;
|
||||||
import org.apollo.util.NameUtil;
|
import org.apollo.util.NameUtil;
|
||||||
@@ -97,6 +98,11 @@ public final class World {
|
|||||||
*/
|
*/
|
||||||
private final EventListenerChainSet events = new EventListenerChainSet();
|
private final EventListenerChainSet events = new EventListenerChainSet();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ScheduledTask that moves Npcs.
|
||||||
|
*/
|
||||||
|
private NpcMovementTask npcMovement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link MobRepository} of {@link Npc}s.
|
* The {@link MobRepository} of {@link Npc}s.
|
||||||
*/
|
*/
|
||||||
@@ -242,8 +248,12 @@ public final class World {
|
|||||||
placeEntities(objects);
|
placeEntities(objects);
|
||||||
logger.fine("Loaded " + objects.length + " static objects.");
|
logger.fine("Loaded " + objects.length + " static objects.");
|
||||||
|
|
||||||
|
npcMovement = new NpcMovementTask(); // Must be exactly here because of ordering issues.
|
||||||
|
scheduler.schedule(npcMovement);
|
||||||
|
|
||||||
manager.start();
|
manager.start();
|
||||||
pluginManager = manager; // TODO move!!
|
pluginManager = manager; // TODO move!!
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -285,6 +295,10 @@ public final class World {
|
|||||||
if (success) {
|
if (success) {
|
||||||
Sector sector = sectors.fromPosition(npc.getPosition());
|
Sector sector = sectors.fromPosition(npc.getPosition());
|
||||||
sector.addEntity(npc);
|
sector.addEntity(npc);
|
||||||
|
|
||||||
|
if (npc.hasBoundaries()) {
|
||||||
|
npcMovement.addNpc(npc);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.warning("Failed to register npc, repository capacity reached: [count=" + npcRepository.size() + "]");
|
logger.warning("Failed to register npc, repository capacity reached: [count=" + npcRepository.size() + "]");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import org.apollo.game.model.area.collision.CollisionMatrix;
|
|||||||
import org.apollo.game.model.entity.Entity;
|
import org.apollo.game.model.entity.Entity;
|
||||||
import org.apollo.game.model.entity.Entity.EntityType;
|
import org.apollo.game.model.entity.Entity.EntityType;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
|
||||||
@@ -175,14 +176,13 @@ public final class Sector {
|
|||||||
Set<Entity> local = entities.get(old);
|
Set<Entity> local = entities.get(old);
|
||||||
|
|
||||||
if (local == null || !local.remove(entity)) {
|
if (local == null || !local.remove(entity)) {
|
||||||
throw new IllegalArgumentException("Entity belongs in this sector but does not exist.");
|
throw new IllegalArgumentException("Entity belongs in this sector (" + this + ") but does not exist.");
|
||||||
}
|
}
|
||||||
|
|
||||||
local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE));
|
local = entities.computeIfAbsent(position, key -> new HashSet<>(DEFAULT_SET_SIZE));
|
||||||
|
|
||||||
local.add(entity);
|
local.add(entity);
|
||||||
notifyListeners(entity, SectorOperation.MOVE);
|
notifyListeners(entity, SectorOperation.MOVE);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -225,9 +225,14 @@ public final class Sector {
|
|||||||
*/
|
*/
|
||||||
public boolean traversable(Position position, EntityType entity, Direction direction) {
|
public boolean traversable(Position position, EntityType entity, Direction direction) {
|
||||||
CollisionMatrix matrix = matrices[position.getHeight()];
|
CollisionMatrix matrix = matrices[position.getHeight()];
|
||||||
int x = position.getLocalX(), y = position.getLocalY();
|
int x = position.getX(), y = position.getY();
|
||||||
|
|
||||||
return matrix.traversable(x, y, entity, direction);
|
return !matrix.untraversable(x % SECTOR_SIZE, y % SECTOR_SIZE, entity, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this).add("coordinates", coordinates).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -166,16 +166,16 @@ public final class CollisionMatrix {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not an Entity of the specified {@link EntityType type} can traverse the tile at the specified
|
* Returns whether or not an Entity of the specified {@link EntityType type} cannot traverse the tile at the
|
||||||
* coordinate pair.
|
* specified coordinate pair.
|
||||||
*
|
*
|
||||||
* @param x The x coordinate.
|
* @param x The x coordinate.
|
||||||
* @param y The y coordinate.
|
* @param y The y coordinate.
|
||||||
* @param entity The {@link EntityType}.
|
* @param entity The {@link EntityType}.
|
||||||
* @param direction The {@link Direction} the Entity is approaching from.
|
* @param direction The {@link Direction} the Entity is approaching from.
|
||||||
* @return {@code true} if the tile at the specified coordinate pair is traversable, {@code false} if not.
|
* @return {@code true} if the tile at the specified coordinate pair is not traversable, {@code false} if not.
|
||||||
*/
|
*/
|
||||||
public boolean traversable(int x, int y, EntityType entity, Direction direction) {
|
public boolean untraversable(int x, int y, EntityType entity, Direction direction) {
|
||||||
CollisionFlag[] flags = CollisionFlag.forType(entity);
|
CollisionFlag[] flags = CollisionFlag.forType(entity);
|
||||||
int north = 0, east = 1, south = 2, west = 3;
|
int north = 0, east = 1, south = 2, west = 3;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.apollo.game.model.entity;
|
package org.apollo.game.model.entity;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.apollo.game.model.Position;
|
import org.apollo.game.model.Position;
|
||||||
@@ -20,9 +19,9 @@ import com.google.common.base.Preconditions;
|
|||||||
public final class Npc extends Mob {
|
public final class Npc extends Mob {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The positions representing the bounds (i.e. walking limits) of this Npc.
|
* The Positions representing the boundaries (i.e. walking limits) of this Npc.
|
||||||
*/
|
*/
|
||||||
private Position[] boundary;
|
private Optional<Position[]> boundaries;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Npc with the specified id and {@link Position}.
|
* Creates a new Npc with the specified id and {@link Position}.
|
||||||
@@ -31,18 +30,20 @@ public final class Npc extends Mob {
|
|||||||
* @param position The position.
|
* @param position The position.
|
||||||
*/
|
*/
|
||||||
public Npc(int id, Position position) {
|
public Npc(int id, Position position) {
|
||||||
this(position, NpcDefinition.lookup(id));
|
this(position, NpcDefinition.lookup(id), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Npc with the specified {@link NpcDefinition} and {@link Position}.
|
* Creates a new Npc with the specified {@link NpcDefinition} and {@link Position}.
|
||||||
*
|
*
|
||||||
* @param position The position.
|
* @param position The Position.
|
||||||
* @param definition The definition.
|
* @param definition The NpcDefinition.
|
||||||
|
* @param boundaries The boundary Positions.
|
||||||
*/
|
*/
|
||||||
public Npc(Position position, NpcDefinition definition) {
|
public Npc(Position position, NpcDefinition definition, Position[] boundaries) {
|
||||||
super(position, definition);
|
super(position, definition);
|
||||||
|
|
||||||
|
this.boundaries = Optional.ofNullable(boundaries);
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,19 +51,19 @@ public final class Npc extends Mob {
|
|||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
if (obj instanceof Npc) {
|
if (obj instanceof Npc) {
|
||||||
Npc other = (Npc) obj;
|
Npc other = (Npc) obj;
|
||||||
return position.equals(other.position) && Arrays.equals(boundary, other.boundary) && getId() == other.getId();
|
return index == other.index && getId() == other.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the boundary of this Npc.
|
* Gets the boundaries of this Npc.
|
||||||
*
|
*
|
||||||
* @return The boundary.
|
* @return The boundaries.
|
||||||
*/
|
*/
|
||||||
public Position[] getBoundary() {
|
public Optional<Position[]> getBoundaries() {
|
||||||
return boundary.clone();
|
return boundaries.isPresent() ? Optional.of(boundaries.get().clone()) : Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -79,30 +80,29 @@ public final class Npc extends Mob {
|
|||||||
return definition.get().getId();
|
return definition.get().getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not this Npc has boundaries.
|
||||||
|
*
|
||||||
|
* @return {@code true} if this Npc has boundaries, {@code false} if not.
|
||||||
|
*/
|
||||||
|
public boolean hasBoundaries() {
|
||||||
|
return boundaries.isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int prime = 31;
|
final int prime = 31;
|
||||||
int result = prime * position.hashCode() + Arrays.hashCode(boundary);
|
return prime * index + getId();
|
||||||
return prime * result + getId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether or not this Npc is bound to a specific set of coordinates.
|
* Sets the boundaries of this Npc.
|
||||||
*
|
*
|
||||||
* @return {@code true} if the Npc is bound, otherwise {@code false}.
|
* @param boundaries The boundaries.
|
||||||
*/
|
*/
|
||||||
public boolean isBound() {
|
public void setBoundaries(Position[] boundaries) {
|
||||||
return boundary == null;
|
Preconditions.checkArgument(boundaries.length == 2, "Boundary count must be 2.");
|
||||||
}
|
this.boundaries = Optional.of(boundaries.clone());
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the boundary of this Npc.
|
|
||||||
*
|
|
||||||
* @param boundary The boundary.
|
|
||||||
*/
|
|
||||||
public void setBoundary(Position[] boundary) {
|
|
||||||
Preconditions.checkArgument(boundary.length == 2, "Boundary count must be 2.");
|
|
||||||
this.boundary = boundary.clone();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import org.apollo.game.model.Position;
|
|||||||
*
|
*
|
||||||
* @author Major
|
* @author Major
|
||||||
*/
|
*/
|
||||||
final class AStarPathfindingAlgorithm extends PathfindingAlgorithm {
|
public final class AStarPathfindingAlgorithm extends PathfindingAlgorithm {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The heuristic.
|
* The heuristic.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package org.apollo.game.model.entity.path;
|
package org.apollo.game.model.entity.path;
|
||||||
|
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.Set;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.apollo.game.model.Direction;
|
import org.apollo.game.model.Direction;
|
||||||
import org.apollo.game.model.Position;
|
import org.apollo.game.model.Position;
|
||||||
@@ -9,7 +9,8 @@ import org.apollo.game.model.World;
|
|||||||
import org.apollo.game.model.area.Sector;
|
import org.apollo.game.model.area.Sector;
|
||||||
import org.apollo.game.model.area.SectorRepository;
|
import org.apollo.game.model.area.SectorRepository;
|
||||||
import org.apollo.game.model.entity.Entity.EntityType;
|
import org.apollo.game.model.entity.Entity.EntityType;
|
||||||
import org.apollo.game.model.entity.GameObject;
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An algorithm used to find a path between two {@link Position}s.
|
* An algorithm used to find a path between two {@link Position}s.
|
||||||
@@ -19,46 +20,48 @@ import org.apollo.game.model.entity.GameObject;
|
|||||||
abstract class PathfindingAlgorithm {
|
abstract class PathfindingAlgorithm {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The repository of sectors.
|
* The repository of Sectors.
|
||||||
*/
|
*/
|
||||||
private static final SectorRepository repository = World.getWorld().getSectorRepository();
|
private static final SectorRepository REPOSITORY = World.getWorld().getSectorRepository();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds a valid path from the origin {@link Position} to the target one.
|
* Finds a valid path from the origin {@link Position} to the target one.
|
||||||
*
|
*
|
||||||
* @param origin The origin position.
|
* @param origin The origin Position.
|
||||||
* @param target The target position.
|
* @param target The target Position.
|
||||||
* @return The {@link Deque} containing the positions to go through.
|
* @return The {@link Deque} containing the Positions to go through.
|
||||||
*/
|
*/
|
||||||
public abstract Deque<Position> find(Position origin, Position target);
|
public abstract Deque<Position> find(Position origin, Position target);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the tile at the specified position is walkable. FIXME do this properly w/tile collision
|
* Returns whether or not a {@link Position} walking one step in any of the specified {@link Direction}s would lead
|
||||||
* data!
|
* to is traversable.
|
||||||
*
|
*
|
||||||
* @param position The {@link Position}.
|
* @param current The current Position.
|
||||||
* @return {@code true} if the tile is walkable, otherwise {@code false}.
|
* @param directions The Directions that should be checked.
|
||||||
|
* @return {@code true} if any of the Directions lead to a traversable tile, otherwise {@code false}.
|
||||||
*/
|
*/
|
||||||
protected boolean traversable(Position position) {
|
protected boolean traversable(Position current, Direction... directions) {
|
||||||
Sector sector = repository.get(position.getSectorCoordinates());
|
return traversable(current, Optional.empty(), directions);
|
||||||
Set<GameObject> objects = sector.getEntities(position, EntityType.GAME_OBJECT);
|
|
||||||
|
|
||||||
return objects.stream().anyMatch(object -> object.getDefinition().isSolid());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the {@link Position}s walking one step in a specified {@link Direction} would lead to is
|
* Returns whether or not a {@link Position} walking one step in any of the specified {@link Direction}s would lead
|
||||||
* traversable.
|
* to is traversable.
|
||||||
*
|
*
|
||||||
* @param position The starting position.
|
* @param current The current Position.
|
||||||
* @param directions The directions that should be checked.
|
* @param boundaries The {@link Optional} containing the Position boundaries.
|
||||||
* @return {@code true} if any of the directions lead to a traversable tile, otherwise {@code false}.
|
* @param directions The Directions that should be checked.
|
||||||
|
* @return {@code true} if any of the Directions lead to a traversable tile, otherwise {@code false}.
|
||||||
*/
|
*/
|
||||||
protected boolean traversable(Position position, Direction... directions) {
|
protected boolean traversable(Position current, Optional<Position[]> boundaries, Direction... directions) {
|
||||||
int height = position.getHeight();
|
Preconditions.checkArgument(directions != null && directions.length > 0, "Directions array cannot be null.");
|
||||||
|
int height = current.getHeight();
|
||||||
|
|
||||||
|
Position[] positions = boundaries.isPresent() ? boundaries.get() : new Position[0];
|
||||||
|
|
||||||
for (Direction direction : directions) {
|
for (Direction direction : directions) {
|
||||||
int x = position.getX(), y = position.getY();
|
int x = current.getX(), y = current.getY();
|
||||||
int value = direction.toInteger();
|
int value = direction.toInteger();
|
||||||
|
|
||||||
if (value >= Direction.NORTH_WEST.toInteger() && value <= Direction.NORTH_EAST.toInteger()) {
|
if (value >= Direction.NORTH_WEST.toInteger() && value <= Direction.NORTH_EAST.toInteger()) {
|
||||||
@@ -73,7 +76,9 @@ abstract class PathfindingAlgorithm {
|
|||||||
x--;
|
x--;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (traversable(new Position(x, y, height))) {
|
Position next = new Position(x, y, height);
|
||||||
|
Sector sector = REPOSITORY.get(next.getSectorCoordinates());
|
||||||
|
if (sector.traversable(next, EntityType.NPC, direction) && (positions.length == 0 || inside(next, positions))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,4 +86,18 @@ abstract class PathfindingAlgorithm {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the specified {@link Position} is inside the specified {@code boundary}.
|
||||||
|
*
|
||||||
|
* @param position The Position.
|
||||||
|
* @param boundary The boundary Positions.
|
||||||
|
* @return {@code true} if the specified Position is inside the boundary, {@code false} if not.
|
||||||
|
*/
|
||||||
|
private boolean inside(Position position, Position[] boundary) {
|
||||||
|
int x = position.getX(), y = position.getY();
|
||||||
|
Position min = boundary[0], max = boundary[1];
|
||||||
|
|
||||||
|
return x >= min.getX() && y >= min.getY() && x <= max.getX() && y <= max.getY();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package org.apollo.game.model.entity.path;
|
|||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.apollo.game.model.Direction;
|
import org.apollo.game.model.Direction;
|
||||||
import org.apollo.game.model.Position;
|
import org.apollo.game.model.Position;
|
||||||
@@ -12,7 +13,12 @@ import org.apollo.game.model.Position;
|
|||||||
*
|
*
|
||||||
* @author Major
|
* @author Major
|
||||||
*/
|
*/
|
||||||
final class SimplePathfindingAlgorithm extends PathfindingAlgorithm {
|
public final class SimplePathfindingAlgorithm extends PathfindingAlgorithm {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Optional containing the boundary Positions.
|
||||||
|
*/
|
||||||
|
private Optional<Position[]> boundaries = Optional.empty();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Deque<Position> find(Position origin, Position target) {
|
public Deque<Position> find(Position origin, Position target) {
|
||||||
@@ -22,6 +28,19 @@ final class SimplePathfindingAlgorithm extends PathfindingAlgorithm {
|
|||||||
return addHorizontal(origin, target, positions);
|
return addHorizontal(origin, target, positions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a valid path from the origin {@link Position} to the target one.
|
||||||
|
*
|
||||||
|
* @param origin The origin Position.
|
||||||
|
* @param target The target Position.
|
||||||
|
* @param boundaries The boundary Positions, which are marking as untraversable.
|
||||||
|
* @return The {@link Deque} containing the Positions to go through.
|
||||||
|
*/
|
||||||
|
public Deque<Position> find(Position origin, Position target, Position[] boundaries) {
|
||||||
|
this.boundaries = Optional.of(boundaries);
|
||||||
|
return find(origin, target);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the necessary and possible horizontal {@link Position}s to the existing {@link Deque}.
|
* Adds the necessary and possible horizontal {@link Position}s to the existing {@link Deque}.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -33,33 +52,33 @@ final class SimplePathfindingAlgorithm extends PathfindingAlgorithm {
|
|||||||
* if so, we traverse horizontally (see {@link #addHorizontal}); if not, return the current path.
|
* if so, we traverse horizontally (see {@link #addHorizontal}); if not, return the current path.
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param current The current position.
|
* @param start The current position.
|
||||||
* @param target The target position.
|
* @param target The target position.
|
||||||
* @param positions The deque of positions.
|
* @param positions The deque of positions.
|
||||||
* @return The deque of positions containing the path.
|
* @return The deque of positions containing the path.
|
||||||
*/
|
*/
|
||||||
private Deque<Position> addHorizontal(Position current, Position target, Deque<Position> positions) {
|
private Deque<Position> addHorizontal(Position start, Position target, Deque<Position> positions) {
|
||||||
int x = current.getX(), y = current.getY(), height = current.getHeight();
|
int x = start.getX(), y = start.getY(), height = start.getHeight();
|
||||||
int dx = x - target.getX();
|
int dx = x - target.getX(), dy = y - target.getY();
|
||||||
|
|
||||||
if (dx > 0) {
|
if (dx > 0) {
|
||||||
Position west = new Position(x - 1, y, height);
|
Position current = start;
|
||||||
|
|
||||||
while (traversable(west) && dx-- > 0) {
|
while (traversable(current, boundaries, Direction.WEST) && dx-- > 0) {
|
||||||
west = new Position(--x, y, height);
|
current = new Position(--x, y, height);
|
||||||
positions.addLast(west);
|
positions.addLast(current);
|
||||||
}
|
}
|
||||||
} else if (dx < 0) {
|
} else if (dx < 0) {
|
||||||
Position east = new Position(x + 1, y, height);
|
Position current = start;
|
||||||
|
|
||||||
while (traversable(east) && dx++ < 0) {
|
while (traversable(current, boundaries, Direction.EAST) && dx++ < 0) {
|
||||||
east = new Position(++x, y, height);
|
current = new Position(++x, y, height);
|
||||||
positions.addLast(east);
|
positions.addLast(current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Position last = new Position(x, y, height);
|
Position last = new Position(x, y, height);
|
||||||
if (!current.equals(last) && traversable(last, Direction.NORTH, Direction.SOUTH)) {
|
if (!start.equals(last) && dy != 0 && traversable(last, boundaries, (dy > 0) ? Direction.SOUTH : Direction.NORTH)) {
|
||||||
return addVertical(last, target, positions);
|
return addVertical(last, target, positions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,33 +96,33 @@ final class SimplePathfindingAlgorithm extends PathfindingAlgorithm {
|
|||||||
* if so, we traverse horizontally (see {@link #addHorizontal}); if not, return the current path.
|
* if so, we traverse horizontally (see {@link #addHorizontal}); if not, return the current path.
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param current The current position.
|
* @param start The current position.
|
||||||
* @param target The target position.
|
* @param target The target position.
|
||||||
* @param positions The deque of positions.
|
* @param positions The deque of positions.
|
||||||
* @return The deque of positions containing the path.
|
* @return The deque of positions containing the path.
|
||||||
*/
|
*/
|
||||||
private Deque<Position> addVertical(Position current, Position target, Deque<Position> positions) {
|
private Deque<Position> addVertical(Position start, Position target, Deque<Position> positions) {
|
||||||
int x = current.getX(), y = current.getY(), height = current.getHeight();
|
int x = start.getX(), y = start.getY(), height = start.getHeight();
|
||||||
int dy = y - target.getY();
|
int dy = y - target.getY(), dx = x - target.getX();
|
||||||
|
|
||||||
if (dy > 0) {
|
if (dy > 0) {
|
||||||
Position south = new Position(x, y - 1, height);
|
Position current = start;
|
||||||
|
|
||||||
while (traversable(south) && dy-- > 0) {
|
while (traversable(current, boundaries, Direction.SOUTH) && dy-- > 0) {
|
||||||
south = new Position(x, --y, height);
|
current = new Position(x, --y, height);
|
||||||
positions.addLast(south);
|
positions.addLast(current);
|
||||||
}
|
}
|
||||||
} else if (dy < 0) {
|
} else if (dy < 0) {
|
||||||
Position north = new Position(x, y + 1, height);
|
Position current = start;
|
||||||
|
|
||||||
while (traversable(north) && dy++ < 0) {
|
while (traversable(current, boundaries, Direction.NORTH) && dy++ < 0) {
|
||||||
north = new Position(x, ++y, height);
|
current = new Position(x, ++y, height);
|
||||||
positions.addLast(north);
|
positions.addLast(current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Position last = new Position(x, y, height);
|
Position last = new Position(x, y, height);
|
||||||
if (!last.equals(target) && traversable(last, Direction.EAST, Direction.WEST)) {
|
if (!last.equals(target) && dx != 0 && traversable(last, boundaries, (dx > 0) ? Direction.WEST : Direction.EAST)) {
|
||||||
return addHorizontal(last, target, positions);
|
return addHorizontal(last, target, positions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package org.apollo.game.scheduling.impl;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.PriorityQueue;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import org.apollo.game.model.Position;
|
||||||
|
import org.apollo.game.model.entity.Npc;
|
||||||
|
import org.apollo.game.model.entity.WalkingQueue;
|
||||||
|
import org.apollo.game.model.entity.path.SimplePathfindingAlgorithm;
|
||||||
|
import org.apollo.game.scheduling.ScheduledTask;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link ScheduledTask} that causes {@link Npc}s to randomly walk around in their boundary.
|
||||||
|
*
|
||||||
|
* @author Major
|
||||||
|
*/
|
||||||
|
public final class NpcMovementTask extends ScheduledTask {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The delay between executions of this task, in pulses.
|
||||||
|
*/
|
||||||
|
private static final int DELAY = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The random number generator used to calculate how many Npcs should be moved per execution.
|
||||||
|
*/
|
||||||
|
private static final Random RANDOM = new Random();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The comparator used to sort the Npcs in the PriorityQueue.
|
||||||
|
*/
|
||||||
|
private static final Comparator<Npc> RANDOM_COMPARATOR = (first, second) -> RANDOM.nextInt(2) - 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The PathfindingAlgorithm used by this Task.
|
||||||
|
*/
|
||||||
|
private final SimplePathfindingAlgorithm algorithm = new SimplePathfindingAlgorithm();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Queue of Npcs.
|
||||||
|
*/
|
||||||
|
private final Queue<Npc> npcs = new PriorityQueue<>(RANDOM_COMPARATOR);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the NpcMovementTask.
|
||||||
|
*/
|
||||||
|
public NpcMovementTask() {
|
||||||
|
super(DELAY, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the {@link Npc} to this {@link ScheduledTask}.
|
||||||
|
*
|
||||||
|
* @param npc The Npc to add.
|
||||||
|
*/
|
||||||
|
public void addNpc(Npc npc) {
|
||||||
|
Preconditions.checkArgument(npc.hasBoundaries(), "Cannot add an npc with no boundaries to the NpcMovementTask.");
|
||||||
|
npcs.offer(npc);
|
||||||
|
System.out.println("Adding npc to movement task: " + npc.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() {
|
||||||
|
int count = RANDOM.nextInt(npcs.size() / 50 + 5);
|
||||||
|
for (int iterations = 0; iterations < count; iterations++) {
|
||||||
|
Npc npc = npcs.poll();
|
||||||
|
if (npc == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Position[] boundary = npc.getBoundaries().get();
|
||||||
|
Position current = npc.getPosition();
|
||||||
|
Position min = boundary[0], max = boundary[1];
|
||||||
|
int currentX = current.getX(), currentY = current.getY();
|
||||||
|
|
||||||
|
boolean negativeX = RANDOM.nextBoolean(), negativeY = RANDOM.nextBoolean();
|
||||||
|
int x = RANDOM.nextInt(negativeX ? (currentX - min.getX()) : (max.getX() - currentX));
|
||||||
|
int y = RANDOM.nextInt(negativeY ? (currentY - min.getY()) : (max.getY() - currentY));
|
||||||
|
|
||||||
|
int dx = negativeX ? -x : x;
|
||||||
|
int dy = negativeY ? -y : y;
|
||||||
|
Position next = new Position(currentX + dx, currentY + dy);
|
||||||
|
|
||||||
|
Deque<Position> positions = algorithm.find(current, next, boundary);
|
||||||
|
WalkingQueue queue = npc.getWalkingQueue();
|
||||||
|
|
||||||
|
Position first = positions.pollFirst();
|
||||||
|
if (first != null && queue.addFirstStep(first)) {
|
||||||
|
positions.forEach(npc.getWalkingQueue()::addStep);
|
||||||
|
}
|
||||||
|
|
||||||
|
npcs.offer(npc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user