package net.scapeemulator.game.model.object;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import net.scapeemulator.cache.def.ObjectDefinition;
import net.scapeemulator.game.model.Entity;
import net.scapeemulator.game.model.Position;
import net.scapeemulator.game.model.area.Area;
import net.scapeemulator.game.model.area.QuadArea;
import net.scapeemulator.game.model.definition.ObjectDefinitions;
/**
* @author Hadyn Richard
* @author David Insley
*/
public final class GroundObjectList {
/**
* The UID counter for all the ground objects that are created.
*/
private static final AtomicInteger counter = new AtomicInteger(1);
/**
* The mapping for all the tiles in this list.
*/
private final Map<Position, Tile> tiles = new LinkedHashMap<>();
/**
* The list of listeners registered to the list.
*/
private final List<GroundObjectListener> listeners = new LinkedList<>();
/**
* The list of objects that have been updated.
*/
private final Set<GroundObject> updatedObjects = new HashSet<>();
/**
* The flag for if objects that were updated should be listed.
*/
private boolean recordUpdates = true;
/**
* The representation of a tile in the game world that contains ground
* objects.
*/
private class Tile {
/**
* The mapping for the objects that exist on the tile.
*/
private Map<ObjectGroup, GroundObject> objects = new LinkedHashMap<>();
/**
* Constructs a new {@link Tile};
*/
public Tile() {
}
public boolean put(ObjectGroup group, GroundObject object, boolean override) {
/* Cannot contain multiple of the same group of object on a tile */
if (!override && objects.containsKey(group)) {
return false;
}
/* Put the object into the mapping */
object.setUid(counter.incrementAndGet());
objects.put(group, object);
/* Update the listeners */
for (GroundObjectListener listener : listeners) {
listener.groundObjectAdded(object);
}
if (recordUpdates) {
/* Add the object to the updated objects set */
updatedObjects.add(object);
}
return true;
}
public GroundObject get(int objectId) {
for (Entry<ObjectGroup, GroundObject> entry : objects.entrySet()) {
GroundObject object = entry.getValue();
if (object.id == objectId) {
return object;
}
}
return null;
}
public GroundObject get(ObjectGroup group) {
return objects.get(group);
}
public boolean isEmpty() {
for (Entry<ObjectGroup, GroundObject> entry : objects.entrySet()) {
GroundObject object = entry.getValue();
if (object.id >= 0) {
return false;
}
}
return true;
}
public boolean contains(int objectId) {
for (Entry<ObjectGroup, GroundObject> entry : objects.entrySet()) {
GroundObject object = entry.getValue();
if (object.id == objectId) {
return true;
}
}
return false;
}
public void remove(ObjectGroup group) {
GroundObject object = objects.remove(group);
if (object != null) {
/* Update the listeners */
for (GroundObjectListener listener : listeners) {
listener.groundObjectRemoved(object);
}
if (recordUpdates) {
/* Remove the object from the updated objects set */
updatedObjects.remove(object);
}
}
}
public List<GroundObject> getObjects() {
return new LinkedList<GroundObject>(objects.values());
}
}
/**
* The representation of a ground object registered to this list.
*/
public class GroundObject extends Entity {
/**
* The data for the ground object.
*/
private int id, animationId, rotation, uid;
/**
* The flag for if the object is hidden.
*/
private boolean isHidden;
/**
* The type of object this ground object is defined as.
*/
private final ObjectType type;
/**
* Constructs a new {@link GroundObject};
*/
public GroundObject(Position position, int id, int animationId, int rotation, ObjectType type) {
this.position = position;
this.id = id;
this.animationId = animationId;
this.rotation = rotation;
this.type = type;
}
/**
* Sets the object id of the ground object.
*/
public void setId(int id) {
if (this.id == id) {
return;
}
int oldId = this.id;
this.id = id;
/* When changing ID the anim id automatically resets */
animationId = getDefinition().getAnimationId();
if (!isHidden) {
/* Update the listeners */
for (GroundObjectListener listener : listeners) {
listener.groundObjectIdUpdated(this, oldId);
}
}
if (recordUpdates) {
/* Add to the updated objects list */
updatedObjects.add(this);
}
}
public void animate(int animationId) {
if (animationId == this.animationId) {
return;
}
this.animationId = animationId;
if (!isHidden) {
for (GroundObjectListener listener : listeners) {
listener.groundObjectAnimated(this);
}
if (recordUpdates) {
/* Add to the updated objects list */
updatedObjects.add(this);
}
}
}
/**
* Gets the object id of the ground object.
*/
public int getId() {
return id;
}
/**
* Rotates an object by a certain amount.
*
* @param amount The amount to rotate the object.
*/
public void rotate(int amount) {
setRotation(rotation + amount);
}
/**
* Sets the rotation of the ground object.
*/
public void setRotation(int rotation) {
rotation &= 3;
if (this.rotation == rotation) {
return;
}
int oldRotation = this.rotation;
this.rotation = rotation;
if (!isHidden) {
/* Update the listeners */
for (GroundObjectListener listener : listeners) {
listener.groundObjectRotationUpdated(this, oldRotation);
}
}
if (recordUpdates) {
/* Add to the updated objects list */
updatedObjects.add(this);
}
}
/**
* Hides the object.
*/
public void hide() {
if (!isHidden) {
/* Alert all the listeners */
for (GroundObjectListener listener : listeners) {
listener.groundObjectRemoved(this);
}
if (recordUpdates) {
/* Add to the updated objects list */
updatedObjects.add(this);
}
isHidden = true;
}
}
/**
* Reveals the object if it is hidden.
*/
public void reveal() {
if (isHidden) {
/* Alert all the listeners */
for (GroundObjectListener listener : listeners) {
listener.groundObjectAdded(this);
}
if (recordUpdates) {
/* Add to the updated objects list */
updatedObjects.add(this);
}
isHidden = false;
}
}
public int getAnimationId() {
return animationId;
}
/**
* Calculates the center Position of this object
*/
public Position getCenterPosition() {
ObjectDefinition def = getDefinition();
int width = def.getWidth();
int length = def.getLength();
if (rotation == 1 || rotation == 3) {
width = def.getLength();
length = def.getWidth();
}
int centerX = position.getX() + (width / 2);
int centerY = position.getY() + (length / 2);
return new Position(centerX, centerY);
}
/**
* Calculates the turn to position.
*/
public Position getTurnToPosition(Position from) {
ObjectDefinition def = getDefinition();
int width = def.getWidth();
int length = def.getLength();
if (rotation == 1 || rotation == 3) {
width = def.getLength();
length = def.getWidth();
}
int turnToX = from.getX(), turnToY = from.getY();
/* Within the width of the object */
if (from.getX() >= position.getX() && from.getX() < position.getX() + width) {
turnToY = position.getY();
}
/* Within the length of the object */
if (from.getY() >= position.getY() && from.getY() < position.getY() + width) {
turnToX = position.getX();
}
/* Upper left corner */
if (from.getX() < position.getX() && from.getY() >= position.getY() + length) {
turnToX = position.getX();
turnToY = position.getY() + length - 1;
}
/* Upper right corner */
if (from.getX() >= position.getX() + width && from.getY() >= position.getY() + length) {
turnToX = position.getX() + width - 1;
turnToY = position.getY() + length - 1;
}
/* Lower left corner */
if (from.getX() < position.getX() + width && from.getY() < position.getY()) {
turnToX = position.getX();
turnToY = position.getY();
}
/* Lower right corner */
if (from.getX() >= position.getX() + width && from.getY() < position.getY()) {
turnToX = position.getX() + width - 1;
turnToY = position.getY();
}
return new Position(turnToX, turnToY);
}
public ObjectDefinition getDefinition() {
return ObjectDefinitions.forId(id);
}
/**
* Gets the rotation of the ground object.
*/
public int getRotation() {
return rotation;
}
/**
* Sets the ground objects unique id.
*/
public void setUid(int uid) {
this.uid = uid;
}
/**
* Gets the ground objects unique id.
*/
public int getUid() {
return uid;
}
/**
* Gets if the object is hidden.
*
* @return If the object is hidden.
*/
public boolean isHidden() {
return isHidden;
}
/**
* Gets the object type.
*/
public ObjectType getType() {
return type;
}
public Set<Position> getValidInteractPositions() {
Set<Position> validPositions = new HashSet<>();
ObjectDefinition def = getDefinition();
int width = def.getWidth();
int length = def.getLength();
if (rotation == 1 || rotation == 3) {
width = def.getLength();
length = def.getWidth();
}
int validSides = def.getValidInteractSides(rotation);
// NORTH
if(((validSides >> 0) & 1) == 0) {
for(int x = 0; x < width; x++) {
validPositions.add(position.copy(x, length));
}
}
// EAST
if(((validSides >> 1) & 1) == 0) {
for(int y = 0; y < length; y++) {
validPositions.add(position.copy(width, y));
}
}
// SOUTH
if(((validSides >> 2) & 1) == 0) {
for(int x = 0; x < width; x++) {
validPositions.add(position.copy(x, -1));
}
}
// WEST
if(((validSides >> 3) & 1) == 0) {
for(int y = 0; y < length; y++) {
validPositions.add(position.copy(-1, y));
}
}
return validPositions;
}
public Area getBounds() {
ObjectDefinition def = getDefinition();
int width = def.getWidth();
int length = def.getLength();
if (rotation == 1 || rotation == 3) {
width = def.getLength();
length = def.getWidth();
}
return new QuadArea(position.getX(), position.getY(), position.getX() + (width - 1), position.getY() + (length - 1));
}
}
/**
* Adds a listener to the list.
*
* @param listener The listener to add.
*/
public void addListener(GroundObjectListener listener) {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
/**
* Removes a listener from the list.
*
* @param listener The listener to remove.
*/
public void removeListener(GroundObjectListener listener) {
listeners.remove(listener);
}
/**
* Sets if the list will record update objects.
*
* @param recordUpdates The flag.
*/
public void setRecordUpdates(boolean recordUpdates) {
this.recordUpdates = recordUpdates;
}
/**
* Fires a ground item added message for each ground object.
*/
public void fireAllEvents(GroundObjectListener listener) {
for (Entry<Position, Tile> entry : tiles.entrySet()) {
for (GroundObject object : entry.getValue().getObjects()) {
if (!object.isHidden) {
listener.groundObjectAdded(object);
if (object.getAnimationId() != object.getDefinition().getAnimationId()) {
listener.groundObjectAnimated(object);
}
} else {
listener.groundObjectRemoved(object);
}
}
}
}
/**
* Fires a ground item added event for each updated ground object.
*/
public void fireEvents(GroundObjectListener listener) {
for (GroundObject object : updatedObjects) {
if (!object.isHidden) {
listener.groundObjectAdded(object);
if (object.getAnimationId() != object.getDefinition().getAnimationId()) {
listener.groundObjectAnimated(object);
}
} else {
listener.groundObjectRemoved(object);
}
}
}
public GroundObject put(Position position, int objectId, int rotation, ObjectType type) {
return put(position, objectId, ObjectDefinitions.forId(objectId).getAnimationId(), rotation, type);
}
public GroundObject put(Position position, int objectId, int rotation, ObjectType type, boolean override) {
return put(position, objectId, ObjectDefinitions.forId(objectId).getAnimationId(), rotation, type, override);
}
public GroundObject put(Position position, int objectId, int animationId, int rotation, ObjectType type) {
return put(position, objectId, animationId, rotation, type, false);
}
public GroundObject put(Position position, int objectId, int animationId, int rotation, ObjectType type, boolean override) {
GroundObject object = new GroundObject(position, objectId, animationId, rotation, type);
Tile tile = tiles.get(position);
if (tile == null) {
tile = new Tile();
tiles.put(position, tile);
}
return tile.put(object.getType().getObjectGroup(), object, override) ? object : null;
}
public List<GroundObject> getAll(Position position) {
Tile tile = tiles.get(position);
if (tile == null) {
return null;
}
return tile.getObjects();
}
public GroundObject get(int objectId, Position position) {
Tile tile = tiles.get(position);
if (tile == null) {
return null;
}
return tile.get(objectId);
}
public GroundObject get(ObjectGroup group, Position position) {
Tile tile = tiles.get(position);
if (tile == null) {
return null;
}
return tile.get(group);
}
public boolean contains(int objectId, Position position) {
Tile tile = tiles.get(position);
if (tile == null) {
return false;
}
return tile.contains(objectId);
}
public boolean isEmpty(Position position) {
Tile tile = tiles.get(position);
if (tile == null) {
return true;
}
return tile.isEmpty();
}
public void remove(Position position, ObjectGroup group) {
Tile tile = tiles.get(position);
if (tile == null) {
return;
}
tile.remove(group);
}
}