Repository reorganisation
Unified kotlin and java into just src/main Unified the rs09 and core packages Took all content out of the core package, and placed it into the new content package Reorganised all source code relating to content to be easier to find and explore
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
package core.game.interaction;
|
||||
|
||||
import core.game.global.action.DoorActionHandler;
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.Entity;
|
||||
import core.game.node.scenery.Scenery;
|
||||
import core.game.world.map.Direction;
|
||||
import core.game.world.map.Location;
|
||||
import core.game.world.map.RegionManager;
|
||||
|
||||
/**
|
||||
* Holds the destination flags for all types of nodes.
|
||||
* @author Emperor
|
||||
*/
|
||||
public class DestinationFlag {
|
||||
|
||||
/**
|
||||
* The Location destination flag.
|
||||
*/
|
||||
public static final DestinationFlag LOCATION = new DestinationFlag();
|
||||
|
||||
/**
|
||||
* The entity destination flag.
|
||||
*/
|
||||
public static final DestinationFlag ENTITY = new DestinationFlag() {
|
||||
|
||||
@Override
|
||||
public Location getDestination(Entity mover, Node n) {
|
||||
Location l = getClosestTo(mover, n, n.getLocation().transform(0, -1, 0));
|
||||
if (mover.size() > 1) {
|
||||
if (l.getX() < n.getLocation().getX()) {
|
||||
l = l.transform(-(mover.size() - 1), 0, 0);
|
||||
}
|
||||
if (l.getY() < n.getLocation().getY()) {
|
||||
l = l.transform(0, -(mover.size() - 1), 0);
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The following an entity destination flag.
|
||||
*/
|
||||
public static final DestinationFlag FOLLOW_ENTITY = new DestinationFlag() {
|
||||
|
||||
@Override
|
||||
public Location getDestination(Entity mover, Node n) {
|
||||
Direction dir = n.getDirection();
|
||||
Location l = n.getLocation().transform(-dir.getStepX(), -dir.getStepY(), 0);
|
||||
if (!checkTraversal(l, dir)) {
|
||||
l = getClosestTo(mover, n, l);
|
||||
}
|
||||
if (mover.size() > 1) {
|
||||
if (l.getX() < n.getLocation().getX()) {
|
||||
l = l.transform(-(mover.size() - 1), 0, 0);
|
||||
}
|
||||
if (l.getY() < n.getLocation().getY()) {
|
||||
l = l.transform(0, -(mover.size() - 1), 0);
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The entity destination flag.
|
||||
*/
|
||||
public static final DestinationFlag COMBAT = new DestinationFlag() {
|
||||
|
||||
@Override
|
||||
public Location getDestination(Entity mover, Node n) {
|
||||
return null;// MovementPulse.getClosest(mover, (Entity) n);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The item destination flag.
|
||||
*/
|
||||
public static final DestinationFlag ITEM = new DestinationFlag() {
|
||||
|
||||
@Override
|
||||
public Location getDestination(Entity mover, Node n) {
|
||||
if (!RegionManager.isTeleportPermitted(n.getLocation())) {
|
||||
return getClosestTo(mover, n, n.getLocation().transform(1, 0, 0));
|
||||
}
|
||||
return n.getLocation();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The object destination flag.
|
||||
*/
|
||||
public static final DestinationFlag OBJECT = new DestinationFlag() {
|
||||
|
||||
@Override
|
||||
public Location getDestination(Entity mover, Node n) {
|
||||
Scenery object = (Scenery) n;
|
||||
if (object.getType() < 4 || object.getType() == 9) {
|
||||
return DoorActionHandler.getDestination(mover, object);
|
||||
}
|
||||
if (object.getType() == 4 || object.getType() == 5) { // Wall
|
||||
// decoration
|
||||
return object.getLocation();
|
||||
}
|
||||
int sizeX = object.getDefinition().sizeX;
|
||||
int sizeY = object.getDefinition().sizeY;
|
||||
if (object.getRotation() % 2 != 0) {
|
||||
int switcher = sizeX;
|
||||
sizeX = sizeY;
|
||||
sizeY = switcher;
|
||||
}
|
||||
Direction dir = Direction.forWalkFlag(object.getDefinition().getWalkingFlag(), object.getRotation());
|
||||
if (dir != null) {
|
||||
return getDestination(mover, object, sizeX, sizeY, dir, 3);
|
||||
}
|
||||
// System.out.println(dir);
|
||||
return getDestination(mover, object, sizeX, sizeY, Direction.getLogicalDirection(object.getLocation(), mover.getLocation()), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the destination for the given object.
|
||||
* @param object The object.
|
||||
* @param dir The preferred direction from the object.
|
||||
* @return The teleporting destination.
|
||||
*/
|
||||
private Location getDestination(Entity mover, Scenery object, int sizeX, int sizeY, Direction dir, int count) {
|
||||
Location closest = null;
|
||||
double distance = 9999.9;
|
||||
Location loc = object.getLocation();
|
||||
for (int i = count; i < 4; i++) {
|
||||
if (dir.toInteger() % 2 != 0) {
|
||||
int x = dir.getStepX();
|
||||
if (x > 0) {
|
||||
x *= sizeX;
|
||||
}
|
||||
for (int y = 0; y < sizeY; y++) {
|
||||
Location l = loc.transform(x, y, 0);
|
||||
if (checkTraversal(l, dir)) {
|
||||
double dist = mover.getLocation().getDistance(l);
|
||||
if (dist < distance) {
|
||||
distance = dist;
|
||||
closest = l;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int y = dir.getStepY();
|
||||
if (y > 0) {
|
||||
y *= sizeY;
|
||||
}
|
||||
for (int x = 0; x < sizeX; x++) {
|
||||
Location l = loc.transform(x, y, 0);
|
||||
if (checkTraversal(l, dir)) {
|
||||
double dist = mover.getLocation().getDistance(l);
|
||||
if (dist < distance) {
|
||||
distance = dist;
|
||||
closest = l;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dir = Direction.get((dir.toInteger() + 1) % 4);
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkTraversal(Location l, Direction dir) {
|
||||
return RegionManager.isTeleportPermitted(l) && dir.canMove(l);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a new {@code DestinationFlag} {@code Object}.
|
||||
*/
|
||||
public DestinationFlag() {
|
||||
/*
|
||||
* empty.
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default destination location.
|
||||
* @param mover The moving entity.
|
||||
* @param node The node to move to.
|
||||
* @return The location to walk to.
|
||||
*/
|
||||
public Location getDestination(Entity mover, Node node) {
|
||||
return node.getLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if traversal is permitted.
|
||||
* @param l The location to check.
|
||||
* @param dir The direction to move.
|
||||
* @return {@code True}.
|
||||
*/
|
||||
public boolean checkTraversal(Location l, Direction dir) {
|
||||
return Direction.get((dir.toInteger() + 2) % 4).canMove(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the closest destination to the current destination, to reach the
|
||||
* node.
|
||||
* @param mover The moving entity.
|
||||
* @param node The node to move to.
|
||||
* @param suggestion The suggested destination location.
|
||||
* @return The destination location.
|
||||
*/
|
||||
public Location getClosestTo(Entity mover, Node node, Location suggestion) {
|
||||
Location nl = node.getLocation();
|
||||
int diffX = suggestion.getX() - nl.getX();
|
||||
int diffY = suggestion.getY() - nl.getY();
|
||||
Direction moveDir = Direction.NORTH;
|
||||
if (diffX < 0) {
|
||||
moveDir = Direction.EAST;
|
||||
} else if (diffX >= node.size()) {
|
||||
moveDir = Direction.WEST;
|
||||
} else if (diffY >= node.size()) {
|
||||
moveDir = Direction.SOUTH;
|
||||
}
|
||||
double distance = 9999.9;
|
||||
Location destination = suggestion;
|
||||
for (int c = 0; c < 4; c++) {
|
||||
for (int i = 0; i < node.size() + 1; i++) {
|
||||
for (int j = 0; j < (i == 0 ? 1 : 2); j++) {
|
||||
Direction current = Direction.get((moveDir.toInteger() + (j == 1 ? 3 : 1)) % 4);
|
||||
Location loc = suggestion.transform(current.getStepX() * i, current.getStepY() * i, 0);
|
||||
if (moveDir.toInteger() % 2 == 0) {
|
||||
if (loc.getX() < nl.getX() || loc.getX() > nl.getX() + node.size() - 1) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (loc.getY() < nl.getY() || loc.getY() > nl.getY() + node.size() - 1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (checkTraversal(loc, moveDir)) {
|
||||
double dist = mover.getLocation().getDistance(loc);
|
||||
if (dist < distance) {
|
||||
distance = dist;
|
||||
destination = loc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
moveDir = Direction.get((moveDir.toInteger() + 1) % 4);
|
||||
int offsetX = Math.abs(moveDir.getStepY() * (node.size() >> 1)); // Not
|
||||
// a
|
||||
// mixup
|
||||
// between
|
||||
// x
|
||||
// &
|
||||
// y!
|
||||
int offsetY = Math.abs(moveDir.getStepX() * (node.size() >> 1));
|
||||
if (moveDir.toInteger() < 2) {
|
||||
suggestion = node.getLocation().transform(-moveDir.getStepX() + offsetX, -moveDir.getStepY() + offsetY, 0);
|
||||
} else {
|
||||
suggestion = node.getLocation().transform(-moveDir.getStepX() * node.size() + offsetX, -moveDir.getStepY() * node.size() + offsetY, 0);
|
||||
}
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package core.game.interaction
|
||||
|
||||
enum class IntType {
|
||||
ITEM,
|
||||
SCENERY,
|
||||
NPC,
|
||||
GROUNDITEM,
|
||||
PLAYER
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
package core.game.interaction;
|
||||
|
||||
import core.game.event.InteractionEvent;
|
||||
import core.game.container.Container;
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.impl.PulseType;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
import core.game.node.scenery.Scenery;
|
||||
import core.game.system.task.Pulse;
|
||||
import core.game.world.update.flag.player.FaceLocationFlag;
|
||||
import core.net.packet.PacketRepository;
|
||||
import core.net.packet.context.InteractionOptionContext;
|
||||
import core.net.packet.out.InteractionOption;
|
||||
import core.tools.SystemLogger;
|
||||
import core.game.world.GameWorld;
|
||||
|
||||
/**
|
||||
* Handles interaction between nodes.
|
||||
* @author Emperor
|
||||
*/
|
||||
public class Interaction {
|
||||
|
||||
/**
|
||||
* The current options.
|
||||
*/
|
||||
private Option[] options = new Option[8];
|
||||
|
||||
/**
|
||||
* The player.
|
||||
*/
|
||||
private final Node node;
|
||||
|
||||
/**
|
||||
* If the interaction option handlers have been initialized.
|
||||
*/
|
||||
private boolean initialized;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Interaction} {@code Object}.
|
||||
* @param node The node reference.
|
||||
*/
|
||||
public Interaction(Node node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the current option.
|
||||
* @param node The node.
|
||||
* @param index The index.
|
||||
* @param name The option name.
|
||||
*/
|
||||
public static void sendOption(Node node, int index, String name) {
|
||||
if (!(node instanceof Player)) {
|
||||
return;
|
||||
}
|
||||
PacketRepository.send(InteractionOption.class, new InteractionOptionContext((Player) node, index, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an interaction option being clicked.
|
||||
* @param player The player using the option.
|
||||
* @param option The option used.
|
||||
*/
|
||||
public void handle(final Player player, final Option option) {
|
||||
try {
|
||||
if (player.getLocks().isInteractionLocked() || option == null) {
|
||||
return;
|
||||
}
|
||||
player.debug("Received interaction request " + option.getName());
|
||||
boolean hasHandler = option.getHandler() != null;
|
||||
boolean walk = hasHandler && option.getHandler().isWalk();
|
||||
if (!walk && hasHandler && option.getHandler().isWalk(player, node)) {
|
||||
walk = true;
|
||||
}
|
||||
if (!hasHandler || walk) {
|
||||
handleWalkOption(player, option, PulseType.STANDARD);
|
||||
} else if (hasHandler) {
|
||||
player.debug("Option handler being used=" + option.getHandler().getClass().getSimpleName());
|
||||
handleDefaultOption(player, option, PulseType.STANDARD);
|
||||
} else {
|
||||
player.getPulseManager().runUnhandledAction(player, PulseType.STANDARD);
|
||||
}
|
||||
player.dispatch(new InteractionEvent(node, option.getName().toLowerCase()));
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
SystemLogger.logErr(this.getClass(), this.getClass().getName() + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an item option.
|
||||
* @param player The player.
|
||||
* @param option The option.
|
||||
* @param container The container the item is in.
|
||||
*/
|
||||
public void handleItemOption(final Player player, final Option option, final Container container) {
|
||||
if (player.getLocks().isInteractionLocked()) {
|
||||
return;
|
||||
}
|
||||
player.getPulseManager().clear(PulseType.STANDARD);
|
||||
GameWorld.getPulser().submit(new Pulse(1, player) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
try {
|
||||
if (player.getLocks().isInteractionLocked() || player.getZoneMonitor().interact(node, option)) {
|
||||
return true;
|
||||
}
|
||||
if(InteractionListeners.run(node.getId(), IntType.ITEM, option.getName(),player,node)){
|
||||
return true;
|
||||
}
|
||||
if (option.getHandler() == null || !option.getHandler().handle(player, node, option.getName().toLowerCase())) {
|
||||
player.getPacketDispatch().sendMessage("Nothing interesting happens.");
|
||||
}
|
||||
if (option.getHandler() != null) {
|
||||
player.debug("Using item handler " + option.getHandler().getClass().getSimpleName());
|
||||
}
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
SystemLogger.logErr(this.getClass(), this.getClass().getName() + e.getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an invalid interaction.
|
||||
* @param player The player.
|
||||
* @param node The node to interact with.
|
||||
*/
|
||||
public static void handleInvalidInteraction(final Player player, final Node node, final Option option) {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
if (node.getLocation() != null) {
|
||||
if (player.getLocks().isMovementLocked()) {
|
||||
return;
|
||||
}
|
||||
player.getPulseManager().run(new MovementPulse(player, node) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
try {
|
||||
player.faceLocation(FaceLocationFlag.getFaceLocation(player, node));
|
||||
if (player.getLocks().isInteractionLocked() || player.getZoneMonitor().interact(node, option)) {
|
||||
return true;
|
||||
}
|
||||
player.getPacketDispatch().sendMessage("Nothing interesting happens.");
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
SystemLogger.logErr(this.getClass(), this.getClass().getName() + e.getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}, PulseType.STANDARD);
|
||||
} else {
|
||||
player.getPulseManager().runUnhandledAction(player, PulseType.STANDARD);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an option requiring the player to walk to the node.
|
||||
* @param player The player.
|
||||
* @param option The option.
|
||||
* @param pulseType The pulse type.
|
||||
*/
|
||||
private void handleWalkOption(final Player player, final Option option, PulseType pulseType) {
|
||||
if (node.getLocation() == null) {
|
||||
player.getPulseManager().runUnhandledAction(player, pulseType);
|
||||
return;
|
||||
}
|
||||
if (player.getLocks().isMovementLocked()) {
|
||||
player.getPulseManager().clear(pulseType);
|
||||
return;
|
||||
}
|
||||
player.getPulseManager().run(new MovementPulse(player, node, option.getHandler()) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
try {
|
||||
player.faceLocation(FaceLocationFlag.getFaceLocation(player, node));
|
||||
if (player.getLocks().isInteractionLocked() || player.getZoneMonitor().interact(node, option)) {
|
||||
return true;
|
||||
}
|
||||
if (option == null || option.getHandler() == null || !option.getHandler().handle(player, node, option.getName().toLowerCase())) {
|
||||
player.getPacketDispatch().sendMessage("Nothing interesting happens.");
|
||||
}
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
SystemLogger.logErr(this.getClass(), this.getClass().getName() + e.getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}, pulseType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a default option.
|
||||
* @param player The player.
|
||||
* @param option The option.
|
||||
* @param pulseType The pulse type.
|
||||
*/
|
||||
private void handleDefaultOption(final Player player, final Option option, PulseType pulseType) {
|
||||
if (!option.getHandler().isDelayed(player)) {
|
||||
if (player.getZoneMonitor().interact(node, option)) {
|
||||
return;
|
||||
}
|
||||
player.getProperties().getCombatPulse().stop();
|
||||
if (!option.getHandler().handle(player, node, option.getName().toLowerCase())) {
|
||||
player.getPulseManager().runUnhandledAction(player, pulseType);
|
||||
}
|
||||
return;
|
||||
}
|
||||
player.getPulseManager().run(new Pulse(1, player, node) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
if (player.getLocks().isInteractionLocked() || player.getZoneMonitor().interact(node, option)) {
|
||||
return true;
|
||||
}
|
||||
if (!option.getHandler().handle(player, node, option.getName().toLowerCase())) {
|
||||
player.getPacketDispatch().sendMessage("Nothing interesting happens.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}, pulseType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the interaction options.
|
||||
* @param nodeId The node id.
|
||||
* @param names The option names.
|
||||
*/
|
||||
public void init(int nodeId, String... names) {
|
||||
options = new Option[names.length];
|
||||
for (int i = 0; i < options.length; i++) {
|
||||
String name = names[i];
|
||||
if (name != null && !name.equals("null")) {
|
||||
set(new Option(name, i).setHandler(Option.defaultHandler(node, nodeId, name)));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default option handlers (if not yet initialized).
|
||||
*/
|
||||
public void setDefault() {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
if (node instanceof Player) {
|
||||
for (int i = 0; i < options.length; i++) {
|
||||
remove(i);
|
||||
}
|
||||
set(Option._P_FOLLOW);
|
||||
set(Option._P_TRADE);
|
||||
set(Option._P_ASSIST);
|
||||
} else if (node instanceof NPC) {
|
||||
NPC npc = (NPC) node;
|
||||
init(npc.getId(), npc.getDefinition().getOptions());
|
||||
} else if (node instanceof Scenery) {
|
||||
Scenery object = (Scenery) node;
|
||||
init(object.getId(), object.getDefinition().getOptions());
|
||||
} else if (node instanceof Item) {
|
||||
Item item = (Item) node;
|
||||
if (item.getLocation() != null) {
|
||||
init(item.getId(), item.getDefinition().getGroundOptions());
|
||||
} else {
|
||||
init(item.getId(), item.getDefinition().getInventoryOptions());
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Unsupported node type - " + node);
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new option.
|
||||
* @param option The option.
|
||||
*/
|
||||
public void set(Option option) {
|
||||
options[option.getIndex()] = option;
|
||||
sendOption(node, option.getIndex(), option.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an option.
|
||||
* @return {@code True} if the option got removed, {@code false} if the
|
||||
* option wasn't set.
|
||||
*/
|
||||
public boolean remove(Option option) {
|
||||
if (options[option.getIndex()] == option) {
|
||||
remove(option.getIndex());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an option.
|
||||
* @param index The index.
|
||||
*/
|
||||
public void remove(int index) {
|
||||
if (options[index] == null) {
|
||||
return;
|
||||
}
|
||||
options[index] = null;
|
||||
sendOption(node, index, "null");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the option on the requested slot.
|
||||
* @param index The slot index.
|
||||
* @return The option on that slot, or {@code null} if there was no option.
|
||||
*/
|
||||
public Option get(int index) {
|
||||
return options[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the options.
|
||||
* @return The options.
|
||||
*/
|
||||
public Option[] getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the initialized value.
|
||||
* @return The initialized.
|
||||
*/
|
||||
public boolean isInitialized() {
|
||||
return initialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initialized value.
|
||||
* @param initialized The initialized to set.
|
||||
*/
|
||||
public void setInitialized(boolean initialized) {
|
||||
this.initialized = initialized;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package core.game.interaction
|
||||
|
||||
import core.api.ContentInterface
|
||||
import core.game.node.Node
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.world.map.Location
|
||||
import content.global.handlers.item.SpadeDigListener
|
||||
|
||||
interface InteractionListener : ContentInterface{
|
||||
val ITEM
|
||||
get() = IntType.ITEM
|
||||
val GROUNDITEM
|
||||
get() = IntType.GROUNDITEM
|
||||
val NPC
|
||||
get() = IntType.NPC
|
||||
val SCENERY
|
||||
get() = IntType.SCENERY
|
||||
|
||||
fun on(id: Int, type: IntType, vararg option: String, handler: (player: Player, node: Node) -> Boolean){
|
||||
InteractionListeners.add(id, type.ordinal, option, handler)
|
||||
}
|
||||
fun on(ids: IntArray, type: IntType, vararg option: String, handler: (player: Player, node: Node) -> Boolean){
|
||||
InteractionListeners.add(ids, type.ordinal, option, handler)
|
||||
}
|
||||
fun on(option: String, type: IntType, handler: (player: Player, node: Node) -> Boolean){
|
||||
InteractionListeners.add(option, type.ordinal, handler)
|
||||
}
|
||||
fun on(type: IntType, vararg option: String, handler: (player: Player, node: Node) -> Boolean){
|
||||
InteractionListeners.add(option, type.ordinal, handler)
|
||||
}
|
||||
fun onUseWith(type: IntType, used: Int, vararg with: Int, handler: (player: Player, used: Node, with: Node) -> Boolean){
|
||||
InteractionListeners.add(type.ordinal, used, with, handler)
|
||||
}
|
||||
fun onUseWith(type: IntType, used: IntArray, vararg with: Int, handler: (player: Player, used: Node, with: Node) -> Boolean){
|
||||
InteractionListeners.add(type.ordinal, used, with, handler)
|
||||
}
|
||||
fun onUseAnyWith(type: IntType, vararg with: Int, handler: (player: Player, used: Node, with: Node) -> Boolean) {
|
||||
InteractionListeners.add(type.ordinal, with, handler)
|
||||
}
|
||||
fun onUseWithPlayer(vararg used: Int, handler: (player: Player, used: Node, with: Node) -> Boolean) {
|
||||
InteractionListeners.add(IntType.PLAYER.ordinal, used, handler)
|
||||
}
|
||||
// Note: wildcard listeners incur overhead on every use-with interaction, only use them as a space-time tradeoff when something
|
||||
// is actually supposed to have a response to every item used with it (e.g. imp boxes, certain quest npcs)
|
||||
fun onUseWithWildcard(type: IntType, predicate: (used: Int, with: Int) -> Boolean, handler: (player: Player, used: Node, with: Node) -> Boolean) {
|
||||
InteractionListeners.addWildcard(type.ordinal, predicate, handler)
|
||||
}
|
||||
fun onEquip(id: Int, handler: (player: Player, node: Node) -> Boolean){
|
||||
InteractionListeners.addEquip(id, handler)
|
||||
}
|
||||
fun onUnequip(id:Int, handler: (player: Player, node: Node) -> Boolean){
|
||||
InteractionListeners.addUnequip(id, handler)
|
||||
}
|
||||
|
||||
fun onEquip(ids: IntArray, handler: (player: Player, node: Node) -> Boolean){
|
||||
ids.forEach { id -> InteractionListeners.addEquip(id, handler) }
|
||||
}
|
||||
fun onUnequip(ids:IntArray, handler: (player: Player, node: Node) -> Boolean){
|
||||
ids.forEach{ id -> InteractionListeners.addUnequip(id, handler) }
|
||||
}
|
||||
|
||||
fun defineDestinationOverrides(){}
|
||||
|
||||
fun setDest(type: IntType, id: Int, handler: (Entity, Node) -> Location){
|
||||
InteractionListeners.addDestOverride(type.ordinal, id, handler)
|
||||
}
|
||||
fun setDest(type: IntType, vararg options: String, handler: (Entity, Node) -> Location){
|
||||
InteractionListeners.addDestOverrides(type.ordinal, options, handler)
|
||||
}
|
||||
|
||||
fun setDest(type: IntType, ids: IntArray, vararg options: String, handler: (Entity, Node) -> Location){
|
||||
InteractionListeners.addDestOverrides(type.ordinal, ids, options, handler)
|
||||
}
|
||||
|
||||
fun onDig(location: Location,method: (player: Player) -> Unit){
|
||||
SpadeDigListener.registerListener(location, method)
|
||||
}
|
||||
|
||||
fun flagInstant() {
|
||||
val name = this::class.java.name
|
||||
InteractionListeners.instantClasses.add(name)
|
||||
}
|
||||
|
||||
fun defineListeners()
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
package core.game.interaction
|
||||
|
||||
import core.game.event.InteractionEvent
|
||||
import core.game.event.UseWithEvent
|
||||
import core.game.node.Node
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.world.map.Location
|
||||
|
||||
object InteractionListeners {
|
||||
private val listeners = HashMap<String,(Player, Node) -> Boolean>(1000)
|
||||
private val useWithListeners = HashMap<String,(Player,Node,Node) -> Boolean>(1000)
|
||||
private val useAnyWithListeners = HashMap<String, (Player, Node, Node) -> Boolean>(10)
|
||||
private val useWithWildcardListeners = HashMap<Int, ArrayList<Pair<(Int, Int) -> Boolean, (Player, Node, Node) -> Boolean>>>(10)
|
||||
private val destinationOverrides = HashMap<String,(Entity, Node) -> Location>(100)
|
||||
private val equipListeners = HashMap<String,(Player,Node) -> Boolean>(10)
|
||||
val instantClasses = HashSet<String>()
|
||||
|
||||
@JvmStatic
|
||||
fun add(id: Int, type: Int, option: Array<out String>, method: (Player,Node) -> Boolean){
|
||||
for(opt in option) {
|
||||
val key = "$id:$type:${opt.toLowerCase()}"
|
||||
if (!validate(key)) {
|
||||
throw IllegalStateException("$opt for $id with type ${IntType.values()[type].name} already defined! Existing use: [${listeners[key]!!::class.toString()}]")
|
||||
}
|
||||
listeners[key] = method
|
||||
}
|
||||
}
|
||||
|
||||
private fun validate(key: String): Boolean {
|
||||
return !listeners.containsKey(key) && !useWithListeners.containsKey(key)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun add(ids: IntArray, type: Int, option: Array<out String>, method: (Player,Node) -> Boolean){
|
||||
for(id in ids){
|
||||
add(id,type,option,method)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun add(option: String,type: Int, method: (Player,Node) -> Boolean){
|
||||
val key = "$type:${option.toLowerCase()}"
|
||||
if (!validate(key)) {
|
||||
throw IllegalStateException("Catchall listener for $option on type ${IntType.values()[type].name} already in use: ${listeners[key]!!::class}")
|
||||
}
|
||||
listeners[key] = method
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun add(options: Array<out String>,type: Int,method: (Player, Node) -> Boolean){
|
||||
for(opt in options){
|
||||
add(opt.toLowerCase(),type,method)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun add(used: Int, with: Int, type: Int, method: (Player,Node,Node) -> Boolean){
|
||||
val key = "$used:$with:$type"
|
||||
val altKey = "$with:$used:$type"
|
||||
if (!validate(key) || !validate(altKey)) {
|
||||
throw IllegalStateException("Usewith using $used with $with for type ${IntType.values()[type]} already in use: [${(useWithListeners[key] ?: useWithListeners[altKey])!!::class}")
|
||||
}
|
||||
useWithListeners[key] = method
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun add(type: Int, used: Int, with: IntArray, method: (Player, Node, Node) -> Boolean){
|
||||
for(id in with){
|
||||
add(used = used, with = id, type = type, method = method)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addWildcard(type: Int, predicate: (used: Int, with: Int) -> Boolean, handler: (player: Player, used: Node, with: Node) -> Boolean) {
|
||||
if(!useWithWildcardListeners.containsKey(type)) {
|
||||
useWithWildcardListeners.put(type, ArrayList())
|
||||
}
|
||||
useWithWildcardListeners[type]!!.add(Pair(predicate, handler))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addEquip(id: Int,method: (Player, Node) -> Boolean){
|
||||
equipListeners["equip:$id"] = method
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addUnequip(id: Int, method: (Player,Node) -> Boolean){
|
||||
equipListeners["unequip:$id"] = method
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getEquip(id: Int): ((Player,Node) -> Boolean)? {
|
||||
return equipListeners["equip:$id"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getUnequip(id: Int): ((Player,Node) -> Boolean)? {
|
||||
return equipListeners["unequip:$id"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun get(used: Int, with: Int, type: Int): ((Player,Node,Node) -> Boolean)? {
|
||||
val method = useWithListeners["$used:$with:$type"] ?: useAnyWithListeners["$with:$type"]
|
||||
if(method != null) {
|
||||
return method
|
||||
}
|
||||
val handlers = useWithWildcardListeners[type] ?: return null
|
||||
for(pair in handlers) {
|
||||
if(pair.first(used, with)) {
|
||||
return pair.second
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun get(id: Int, type: Int, option: String): ((Player,Node) -> Boolean)?{
|
||||
return listeners["$id:$type:${option.toLowerCase()}"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun get(option: String,type: Int): ((Player,Node) -> Boolean)?{
|
||||
return listeners["$type:${option.toLowerCase()}"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addDestOverride(type: Int, id: Int, method: (Entity,Node) -> Location){
|
||||
destinationOverrides["$type:$id"] = method
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addDestOverrides(type: Int,options: Array<out String>, method: (Entity,Node) -> Location){
|
||||
for(opt in options){
|
||||
destinationOverrides["$type:${opt.toLowerCase()}"] = method
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addDestOverrides(type: Int, ids: IntArray,options: Array<out String>, method: (Entity,Node) -> Location){
|
||||
for(id in ids){
|
||||
for(opt in options){
|
||||
destinationOverrides["$type:$id:${opt.toLowerCase()}"] = method
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOverride(type: Int, id:Int, option: String): ((Entity, Node) -> Location)?{
|
||||
return destinationOverrides["$type:$id:${option.toLowerCase()}"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOverride(type: Int,id: Int): ((Entity,Node) -> Location)?{
|
||||
return destinationOverrides["$type:$id"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOverride(type: Int,option: String): ((Entity,Node) -> Location)?{
|
||||
return destinationOverrides["$type:$option"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun run(id: Int, player: Player, node: Node, isEquip: Boolean): Boolean{
|
||||
player.dispatch(InteractionEvent(node, if(isEquip) "equip" else "unequip"))
|
||||
if(isEquip){
|
||||
return equipListeners["equip:$id"]?.invoke(player,node) ?: true
|
||||
} else {
|
||||
return equipListeners["unequip:$id"]?.invoke(player,node) ?: true
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun run(used: Node, with: Node, type: IntType, player: Player): Boolean{
|
||||
val flag = when(type){
|
||||
IntType.NPC, IntType.PLAYER -> DestinationFlag.ENTITY
|
||||
IntType.SCENERY -> DestinationFlag.OBJECT
|
||||
IntType.GROUNDITEM -> DestinationFlag.ITEM
|
||||
else -> DestinationFlag.OBJECT
|
||||
}
|
||||
|
||||
if(player.locks.isInteractionLocked) return false
|
||||
|
||||
var flipped = false
|
||||
|
||||
val method = if (with is Player) get(-1, used.id, 4) ?: return false
|
||||
else get(used.id,with.id,type.ordinal) ?:
|
||||
if (type == IntType.ITEM)
|
||||
get(with.id,used.id,type.ordinal).also { flipped = true } ?: return false
|
||||
else return false
|
||||
|
||||
val destOverride = if(flipped) {
|
||||
getOverride(type.ordinal, used.id, "use") ?: getOverride(type.ordinal, with.id) ?: getOverride(type.ordinal, "use")
|
||||
} else {
|
||||
getOverride(type.ordinal, with.id, "use") ?: getOverride(type.ordinal, used.id) ?: getOverride(type.ordinal, "use")
|
||||
}
|
||||
|
||||
|
||||
if(type != IntType.ITEM && !isUseWithInstant(method)) {
|
||||
if(player.locks.isMovementLocked) return false
|
||||
player.pulseManager.run(object : MovementPulse(player, with, flag, destOverride) {
|
||||
override fun pulse(): Boolean {
|
||||
if (player.zoneMonitor.useWith(used.asItem(), with)) {
|
||||
return true
|
||||
}
|
||||
player.faceLocation(with.location)
|
||||
if(flipped) player.dispatch(UseWithEvent(with.id, used.id))
|
||||
else player.dispatch(UseWithEvent(used.id, with.id))
|
||||
if(flipped) method.invoke(player,with,used)
|
||||
else method.invoke(player,used,with)
|
||||
return true
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if(flipped) player.dispatch(UseWithEvent(with.id, used.id))
|
||||
else player.dispatch(UseWithEvent(used.id, with.id))
|
||||
if(flipped) method.invoke(player,with,used)
|
||||
else method.invoke(player,used,with)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun run(id: Int, type: IntType, option: String, player: Player, node: Node): Boolean{
|
||||
val flag = when(type){
|
||||
IntType.PLAYER -> DestinationFlag.ENTITY
|
||||
IntType.GROUNDITEM -> DestinationFlag.ITEM
|
||||
IntType.NPC -> DestinationFlag.ENTITY
|
||||
IntType.SCENERY -> null
|
||||
else -> DestinationFlag.OBJECT
|
||||
}
|
||||
|
||||
if(player.locks.isInteractionLocked) return false
|
||||
|
||||
val method = get(id,type.ordinal,option) ?: get(option,type.ordinal) ?: return false
|
||||
val destOverride = getOverride(type.ordinal, id, option) ?: getOverride(type.ordinal,node.id) ?: getOverride(type.ordinal,option.toLowerCase())
|
||||
|
||||
player.setAttribute("interact:option", option)
|
||||
|
||||
if(option.toLowerCase() == "attack") //Attack needs special handling >.>
|
||||
{
|
||||
player.dispatch(InteractionEvent(node, option.toLowerCase()))
|
||||
method.invoke(player, node)
|
||||
return true
|
||||
}
|
||||
|
||||
if(type != IntType.ITEM && !isInstant(method)) {
|
||||
if(player.locks.isMovementLocked) return false
|
||||
player.pulseManager.run(object : MovementPulse(player, node, flag, destOverride) {
|
||||
override fun pulse(): Boolean {
|
||||
if(player.zoneMonitor.interact(node, Option(option, 0))) return true
|
||||
player.faceLocation(node.location)
|
||||
player.dispatch(InteractionEvent(node, option.toLowerCase()))
|
||||
method.invoke(player,node)
|
||||
return true
|
||||
}
|
||||
})
|
||||
} else {
|
||||
method.invoke(player,node)
|
||||
player.dispatch(InteractionEvent(node, option.toLowerCase()))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun add(type: Int, used: IntArray, with: IntArray, handler: (Player, Node, Node) -> Boolean) {
|
||||
for(u in used){
|
||||
for (w in with){
|
||||
useWithListeners["$u:$w:$type"] = handler
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun add(type: Int, with: IntArray, handler: (Player, Node, Node) -> Boolean) {
|
||||
for (w in with) {
|
||||
useAnyWithListeners["$w:$type"] = handler
|
||||
}
|
||||
}
|
||||
|
||||
fun isInstant(handler: (Player, Node) -> Boolean): Boolean {
|
||||
val className = handler.javaClass.name.substringBefore("$")
|
||||
return instantClasses.contains(className)
|
||||
}
|
||||
|
||||
fun isUseWithInstant(handler: (player: Player, used: Node, with: Node) -> Boolean): Boolean {
|
||||
val className = handler.javaClass.name.substringBefore("$")
|
||||
return instantClasses.contains(className)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package core.game.interaction
|
||||
|
||||
import core.api.ContentInterface
|
||||
import core.game.component.Component
|
||||
import core.game.node.entity.player.Player
|
||||
|
||||
/**
|
||||
* An interface for writing content that allows the class to handle game interface interactions
|
||||
*
|
||||
* Interactions should be defined in the required [defineInterfaceListeners] method.
|
||||
*/
|
||||
interface InterfaceListener : ContentInterface {
|
||||
fun defineInterfaceListeners()
|
||||
|
||||
fun on(componentID: Int, buttonID: Int, handler: (player: Player, component: Component, opcode: Int, buttonID: Int, slot: Int, itemID: Int) -> Boolean){
|
||||
InterfaceListeners.add(componentID, buttonID, handler)
|
||||
}
|
||||
fun on(componentID: Int, handler: (player: Player, component: Component, opcode: Int, buttonID: Int, slot: Int, itemID: Int) -> Boolean){
|
||||
InterfaceListeners.add(componentID, handler)
|
||||
}
|
||||
fun onOpen(componentID: Int,handler: (player: Player, component: Component) -> Boolean){
|
||||
InterfaceListeners.addOpenListener(componentID, handler)
|
||||
}
|
||||
fun onClose(componentID: Int,handler: (player: Player, component: Component) -> Boolean){
|
||||
InterfaceListeners.addCloseListener(componentID, handler)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package core.game.interaction
|
||||
|
||||
import core.game.component.Component
|
||||
import core.game.node.entity.player.Player
|
||||
|
||||
object InterfaceListeners {
|
||||
val buttonListeners = HashMap<String,(Player, Component, Int, Int, Int, Int) -> Boolean>(1000)
|
||||
val openListeners = HashMap<String,(Player,Component) -> Boolean>(100)
|
||||
|
||||
@JvmStatic
|
||||
fun add(componentID: Int, buttonID: Int, handler: (Player,Component,Int,Int,Int,Int) -> Boolean){
|
||||
buttonListeners["$componentID:$buttonID"] = handler
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun add(componentID: Int, handler: (Player, Component, Int, Int, Int, Int) -> Boolean){
|
||||
buttonListeners["$componentID"] = handler
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addOpenListener(componentID: Int, handler: (Player,Component) -> Boolean){
|
||||
openListeners["$componentID"] = handler
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun addCloseListener(componentID: Int, handler: (Player, Component) -> Boolean){
|
||||
openListeners["close:$componentID"] = handler
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun get(componentID: Int, buttonID: Int): ((Player,Component,Int,Int,Int,Int) -> Boolean)?{
|
||||
return buttonListeners["$componentID:$buttonID"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun get(componentID: Int): ((Player,Component,Int,Int,Int,Int) -> Boolean)?{
|
||||
return buttonListeners["$componentID"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getOpenListener(componentID: Int): ((Player,Component) -> Boolean)?{
|
||||
return openListeners["$componentID"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getCloseListener(componentID: Int): ((Player,Component) -> Boolean)?{
|
||||
return openListeners["close:$componentID"]
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun runOpen(player: Player,component: Component): Boolean{
|
||||
val method = getOpenListener(component.id) ?: return false
|
||||
return method.invoke(player,component)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun runClose(player: Player,component: Component): Boolean{
|
||||
val method = getCloseListener(component.id) ?: return true
|
||||
return method.invoke(player,component)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun run(player: Player, component: Component, opcode: Int, buttonID: Int, slot: Int, itemID: Int): Boolean{
|
||||
val method = get(component.id,buttonID) ?: get(component.id) ?: return false
|
||||
return method.invoke(player,component,opcode,buttonID,slot,itemID)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package core.game.interaction
|
||||
|
||||
import core.api.StartupListener
|
||||
|
||||
interface Listener : StartupListener {
|
||||
override fun startup() {
|
||||
defineListeners()
|
||||
}
|
||||
|
||||
fun defineListeners()
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
package core.game.interaction;
|
||||
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.Entity;
|
||||
import core.game.node.entity.impl.WalkingQueue;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.system.task.Pulse;
|
||||
import core.game.world.map.Direction;
|
||||
import core.game.world.map.Location;
|
||||
import core.game.world.map.Point;
|
||||
import core.game.world.map.path.Path;
|
||||
import core.game.world.map.path.Pathfinder;
|
||||
import core.net.packet.PacketRepository;
|
||||
import core.net.packet.context.PlayerContext;
|
||||
import core.net.packet.out.ClearMinimapFlag;
|
||||
import kotlin.jvm.functions.Function2;
|
||||
import core.tools.SystemLogger;
|
||||
|
||||
import java.util.Deque;
|
||||
|
||||
/**
|
||||
* Handles a movement task.
|
||||
*
|
||||
* @author Emperor
|
||||
*/
|
||||
public abstract class MovementPulse extends Pulse {
|
||||
|
||||
/**
|
||||
* The moving entity.
|
||||
*/
|
||||
protected Entity mover;
|
||||
|
||||
/**
|
||||
* The destination node.
|
||||
*/
|
||||
protected Node destination;
|
||||
|
||||
/**
|
||||
* The destination's last location.
|
||||
*/
|
||||
private Location last;
|
||||
|
||||
/**
|
||||
* The pathfinder.
|
||||
*/
|
||||
private Pathfinder pathfinder;
|
||||
|
||||
/**
|
||||
* If running should be forced.
|
||||
*/
|
||||
private boolean forceRun;
|
||||
|
||||
/**
|
||||
* The option handler.
|
||||
*/
|
||||
private OptionHandler optionHandler;
|
||||
|
||||
/**
|
||||
* The use with handler.
|
||||
*/
|
||||
private UseWithHandler useHandler;
|
||||
|
||||
/**
|
||||
* The destination flag.
|
||||
*/
|
||||
private DestinationFlag destinationFlag;
|
||||
|
||||
/**
|
||||
* The location to interact from.
|
||||
*/
|
||||
private Location interactLocation;
|
||||
|
||||
/**
|
||||
* If the path couldn't be fully found.
|
||||
*/
|
||||
private boolean near;
|
||||
|
||||
private Function2<Entity,Node,Location> overrideMethod;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code MovementPulse} {@code Object}.
|
||||
*
|
||||
* @param mover The moving entity.
|
||||
* @param destination The destination node.
|
||||
*/
|
||||
public MovementPulse(Entity mover, Node destination) {
|
||||
this(mover, destination, null, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code MovementPulse} {@code Object}.
|
||||
*
|
||||
* @param mover The moving entity.
|
||||
* @param destination The destination node.
|
||||
* @param forceRun If the entity is forced to run.
|
||||
*/
|
||||
public MovementPulse(Entity mover, Node destination, boolean forceRun) {
|
||||
this(mover, destination, null, forceRun);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code MovementPulse} {@code Object}.
|
||||
*
|
||||
* @param mover The moving entity.
|
||||
* @param destination The destination node.
|
||||
* @param pathfinder The pathfinder to use.
|
||||
*/
|
||||
public MovementPulse(Entity mover, Node destination, Pathfinder pathfinder) {
|
||||
this(mover, destination, pathfinder, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code MovementPulse} {@code Object}.
|
||||
*
|
||||
* @param mover The moving entity.
|
||||
* @param destination The destination node.
|
||||
* @param optionHandler The option handler used.
|
||||
*/
|
||||
public MovementPulse(Entity mover, Node destination, OptionHandler optionHandler) {
|
||||
this(mover, destination, null, false);
|
||||
this.optionHandler = optionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code MovementPulse} {@code Object}.
|
||||
*
|
||||
* @param mover The moving entity.
|
||||
* @param destination The destination node.
|
||||
* @param useHandler The use with handler used.
|
||||
*/
|
||||
public MovementPulse(Entity mover, Node destination, UseWithHandler useHandler) {
|
||||
this(mover, destination, null, false);
|
||||
this.useHandler = useHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code MovementPulse} {@code Object}.
|
||||
*
|
||||
* @param mover The moving entity.
|
||||
* @param destination The destination node.
|
||||
* @param destinationFlag The destination flag.
|
||||
*/
|
||||
public MovementPulse(Entity mover, Node destination, DestinationFlag destinationFlag) {
|
||||
this(mover, destination, null, false);
|
||||
this.destinationFlag = destinationFlag;
|
||||
}
|
||||
|
||||
public MovementPulse(Entity mover, Node destination, DestinationFlag destinationFlag, Function2<Entity,Node,Location> method){
|
||||
this(mover,destination,null,false);
|
||||
this.destinationFlag = destinationFlag;
|
||||
this.overrideMethod = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code MovementPulse} {@code Object}.
|
||||
*
|
||||
* @param mover The moving entity.
|
||||
* @param destination The destination node.
|
||||
* @param pathfinder The pathfinder to use.
|
||||
* @param forceRun If the entity is forced to run.
|
||||
*/
|
||||
public MovementPulse(Entity mover, Node destination, Pathfinder pathfinder, boolean forceRun) {
|
||||
super(1, mover, destination);
|
||||
this.mover = mover;
|
||||
this.destination = destination;
|
||||
if (pathfinder == null) {
|
||||
if (mover instanceof Player) {
|
||||
this.pathfinder = Pathfinder.SMART;
|
||||
} else {
|
||||
this.pathfinder = Pathfinder.DUMB;
|
||||
}
|
||||
} else {
|
||||
this.pathfinder = pathfinder;
|
||||
}
|
||||
this.forceRun = forceRun;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean update() {
|
||||
mover.face(null);
|
||||
if (mover == null || destination == null || mover.getViewport().getRegion() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasInactiveNode() || !mover.getViewport().getRegion().isActive()) {
|
||||
stop();
|
||||
return true;
|
||||
}
|
||||
if (!isRunning()) {
|
||||
return true;
|
||||
}
|
||||
findPath();
|
||||
Location ml = mover.getLocation();
|
||||
// Allow being within 1 square of moving entities to interact with them.
|
||||
int radius = destination instanceof Entity && ((Entity)destination).getWalkingQueue().hasPath() ? 1 : 0;
|
||||
if (Math.max(Math.abs(ml.getX() - interactLocation.getX()), Math.abs(ml.getY() - interactLocation.getY())) <= radius) {
|
||||
try {
|
||||
if (near || pulse()) {
|
||||
if (mover instanceof Player) {
|
||||
if (near) {
|
||||
((Player) mover).getPacketDispatch().sendMessage("I can't reach that.");
|
||||
}
|
||||
PacketRepository.send(ClearMinimapFlag.class, new PlayerContext((Player) mover));
|
||||
}
|
||||
stop();
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
stop();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
if (destination instanceof Entity) {
|
||||
mover.face(null);
|
||||
}
|
||||
last = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a path to the destination, if necessary.
|
||||
*/
|
||||
public void findPath() {
|
||||
if (mover instanceof NPC && mover.asNpc().isNeverWalks()) {
|
||||
return;
|
||||
}
|
||||
if(destination.getLocation() == null){
|
||||
SystemLogger.logAlert(this.getClass(), destination.getId() + " < ID");
|
||||
SystemLogger.logAlert(this.getClass(), destination.getName() + " < NAME");
|
||||
SystemLogger.logAlert(this.getClass(), "ASDAD");
|
||||
return;
|
||||
}
|
||||
boolean inside = isInsideEntity(mover.getLocation());
|
||||
/* This appears to have been a premature optimization that lead to a bug that would cause both entities
|
||||
to completely stop moving mid-combat/mid-follow-dance/etc
|
||||
if (last != null && last.equals(destination.getLocation()) && !inside) {
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
Location loc = null;
|
||||
|
||||
if (destinationFlag != null && overrideMethod == null) {
|
||||
loc = destinationFlag.getDestination(mover, destination);
|
||||
}
|
||||
else if(overrideMethod != null){
|
||||
loc = overrideMethod.invoke(mover,destination);
|
||||
if(loc == destination.getLocation()) loc = destinationFlag.getDestination(mover,destination);
|
||||
}
|
||||
|
||||
if (loc == null) {
|
||||
if (optionHandler != null) {
|
||||
loc = optionHandler.getDestination(mover, destination);
|
||||
}
|
||||
else if (useHandler != null) {
|
||||
loc = useHandler.getDestination((Player) mover, destination);
|
||||
}
|
||||
else if (inside) {
|
||||
loc = findBorderLocation();
|
||||
}
|
||||
}
|
||||
|
||||
if (destination == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Location ml = mover.getLocation();
|
||||
Location dl = destination.getLocation();
|
||||
// Lead the target if they're walking/running, unless they're already within interaction range
|
||||
if(loc != null && destination instanceof Entity && Math.max(Math.abs(ml.getX() - dl.getX()), Math.abs(ml.getY() - dl.getY())) > 1) {
|
||||
WalkingQueue wq = ((Entity)destination).getWalkingQueue();
|
||||
if(wq.hasPath()) {
|
||||
Point[] points = wq.getQueue().toArray(new Point[0]);
|
||||
if(points.length > 0) {
|
||||
Point p = points[0];
|
||||
for(int i=0; i<points.length; i++) {
|
||||
// Target the farthest point along target's planned movement that's within 1 tick's running,
|
||||
// this ensures the player will run to catch up to the target if able.
|
||||
if(Math.max(Math.abs(ml.getX() - points[i].getX()), Math.abs(ml.getY() - points[i].getY())) <= 2) {
|
||||
p = points[i];
|
||||
}
|
||||
}
|
||||
loc.setX(p.getX());
|
||||
loc.setY(p.getY());
|
||||
}
|
||||
}
|
||||
}
|
||||
Path path = Pathfinder.find(mover, loc != null ? loc : destination, true, pathfinder);
|
||||
near = !path.isSuccessful() || path.isMoveNear();
|
||||
interactLocation = mover.getLocation();
|
||||
boolean canMove = true;
|
||||
if (destination instanceof NPC || destination instanceof Player) {
|
||||
Entity e = (Entity) destination;
|
||||
Location l = e.getLocation();
|
||||
Deque<Point> npcPath = e.getWalkingQueue().getQueue();
|
||||
if (e.getWalkingQueue().hasPath() && e.getProperties().getCombatPulse().isRunning() && e.getProperties().getCombatPulse().getVictim() == mover)
|
||||
canMove = false;
|
||||
if (!canMove) { //If we normally shouldn't move, but the NPC's pathfinding is not letting them move, then move.
|
||||
if (npcPath.size() == 1) {
|
||||
Point pathElement = npcPath.peek();
|
||||
if (pathElement.getX() == l.getX() && pathElement.getY() == l.getY())
|
||||
canMove = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!path.getPoints().isEmpty() && canMove) {
|
||||
Point point = path.getPoints().getLast();
|
||||
interactLocation = Location.create(point.getX(), point.getY(), mover.getLocation().getZ());
|
||||
if (forceRun) {
|
||||
mover.getWalkingQueue().reset(forceRun);
|
||||
} else {
|
||||
mover.getWalkingQueue().reset();
|
||||
}
|
||||
int size = path.getPoints().toArray().length;
|
||||
Deque points = path.getPoints();
|
||||
for (int i = 0; i < size; i++) {
|
||||
point = path.getPoints().pop();
|
||||
mover.getWalkingQueue().addPath(point.getX(), point.getY());
|
||||
if (destination instanceof Entity) {
|
||||
mover.face((Entity) destination);
|
||||
} else {
|
||||
mover.face(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
last = destination.getLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the closest location next to the node.
|
||||
*
|
||||
* @return The location to walk to.
|
||||
*/
|
||||
private Location findBorderLocation() {
|
||||
int size = destination.size();
|
||||
Location centerDest = destination.getLocation().transform(size >> 1, size >> 1, 0);
|
||||
Location center = mover.getLocation().transform(mover.size() >> 1, mover.size() >> 1, 0);
|
||||
Direction direction = Direction.getLogicalDirection(centerDest, center);
|
||||
Location delta = Location.getDelta(destination.getLocation(), mover.getLocation());
|
||||
main:
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int amount = 0;
|
||||
switch (direction) {
|
||||
case NORTH:
|
||||
amount = size - delta.getY();
|
||||
break;
|
||||
case EAST:
|
||||
amount = size - delta.getX();
|
||||
break;
|
||||
case SOUTH:
|
||||
amount = mover.size() + delta.getY();
|
||||
break;
|
||||
case WEST:
|
||||
amount = mover.size() + delta.getX();
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
for (int j = 0; j < amount; j++) {
|
||||
for (int s = 0; s < mover.size(); s++) {
|
||||
switch (direction) {
|
||||
case NORTH:
|
||||
if (!direction.canMove(mover.getLocation().transform(s, j + mover.size(), 0))) {
|
||||
direction = Direction.get((direction.toInteger() + 1) & 3);
|
||||
continue main;
|
||||
}
|
||||
break;
|
||||
case EAST:
|
||||
if (!direction.canMove(mover.getLocation().transform(j + mover.size(), s, 0))) {
|
||||
direction = Direction.get((direction.toInteger() + 1) & 3);
|
||||
continue main;
|
||||
}
|
||||
break;
|
||||
case SOUTH:
|
||||
if (!direction.canMove(mover.getLocation().transform(s, -(j + 1), 0))) {
|
||||
direction = Direction.get((direction.toInteger() + 1) & 3);
|
||||
continue main;
|
||||
}
|
||||
break;
|
||||
case WEST:
|
||||
if (!direction.canMove(mover.getLocation().transform(-(j + 1), s, 0))) {
|
||||
direction = Direction.get((direction.toInteger() + 1) & 3);
|
||||
continue main;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Location location = mover.getLocation().transform(direction, amount);
|
||||
return location;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the mover is standing on an invalid position.
|
||||
*
|
||||
* @param l The location.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
private boolean isInsideEntity(Location l) {
|
||||
if (!(destination instanceof Entity)) {
|
||||
return false;
|
||||
}
|
||||
if (((Entity) destination).getWalkingQueue().isMoving()) {
|
||||
return false;
|
||||
}
|
||||
Location loc = destination.getLocation();
|
||||
int size = destination.size();
|
||||
return Pathfinder.isStandingIn(l.getX(), l.getY(), mover.size(), mover.size(), loc.getX(), loc.getY(), size, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the forceRun.
|
||||
*
|
||||
* @return The forceRun.
|
||||
*/
|
||||
public boolean isForceRun() {
|
||||
return forceRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the forceRun.
|
||||
*
|
||||
* @param forceRun The forceRun to set.
|
||||
*/
|
||||
public void setForceRun(boolean forceRun) {
|
||||
this.forceRun = forceRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current destination.
|
||||
*
|
||||
* @param destination The destination.
|
||||
*/
|
||||
public void setDestination(Node destination) {
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the last location.
|
||||
*
|
||||
* @param last The last location.
|
||||
*/
|
||||
public void setLast(Location last) {
|
||||
this.last = last;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package core.game.interaction;
|
||||
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
|
||||
/**
|
||||
* Represents a node use-with (other node) option.
|
||||
* @author Emperor
|
||||
*/
|
||||
public final class NodeUsageEvent {
|
||||
|
||||
/**
|
||||
* The player.
|
||||
*/
|
||||
private final Player player;
|
||||
|
||||
/**
|
||||
* The component id.
|
||||
*/
|
||||
private final int componentId;
|
||||
|
||||
/**
|
||||
* The used node.
|
||||
*/
|
||||
private final Node used;
|
||||
|
||||
/**
|
||||
* The node we used the other node on.
|
||||
*/
|
||||
private final Node with;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code NodeUsageEvent} {@code Object}.
|
||||
* @param player The player.
|
||||
* @param componentId The component id.
|
||||
* @param used The used node.
|
||||
* @param with The node the other node is used on.
|
||||
*/
|
||||
public NodeUsageEvent(Player player, int componentId, Node used, Node with) {
|
||||
this.player = player;
|
||||
this.componentId = componentId;
|
||||
this.used = used;
|
||||
this.with = with;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the base item.
|
||||
* @return The base item.
|
||||
*/
|
||||
public Item getBaseItem() {
|
||||
return with instanceof Item ? (Item) with : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the used item.
|
||||
* @return The used item.
|
||||
*/
|
||||
public Item getUsedItem() {
|
||||
return used instanceof Item ? (Item) used : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player.
|
||||
* @return The player.
|
||||
*/
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the componentId.
|
||||
* @return The componentId.
|
||||
*/
|
||||
public int getComponentId() {
|
||||
return componentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the used.
|
||||
* @return The used.
|
||||
*/
|
||||
public Node getUsed() {
|
||||
return used;
|
||||
}
|
||||
|
||||
/**
|
||||
* The node the other node is used on.
|
||||
* @return The node.
|
||||
*/
|
||||
public Node getUsedWith() {
|
||||
return with;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package core.game.interaction;
|
||||
|
||||
import core.cache.def.impl.ItemDefinition;
|
||||
import core.cache.def.impl.NPCDefinition;
|
||||
import core.cache.def.impl.SceneryDefinition;
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.item.Item;
|
||||
import core.game.node.scenery.Scenery;
|
||||
|
||||
/**
|
||||
* Represents an Interaction option.
|
||||
* @author Emperor
|
||||
*/
|
||||
public final class Option {
|
||||
|
||||
/**
|
||||
* The player attack option.
|
||||
*/
|
||||
public static final Option _P_ATTACK = new Option("Attack", 0);
|
||||
|
||||
/**
|
||||
* The player follow option.
|
||||
*/
|
||||
public static final Option _P_FOLLOW = new Option("Follow", 2);
|
||||
|
||||
/**
|
||||
* The player trade option.
|
||||
*/
|
||||
public static final Option _P_TRADE = new Option("Trade with", 3);
|
||||
public static final Option _P_GIVETO = new Option("Give-to", 3);
|
||||
|
||||
public static final Option _P_PICKPOCKET = new Option("Pickpocket", 4);
|
||||
|
||||
|
||||
public static final Option _P_EXAMINE = new Option("Examine", 7);
|
||||
|
||||
/**
|
||||
* The player assist option.
|
||||
*/
|
||||
public static final Option _P_ASSIST = new Option("Req Assist", 6);
|
||||
|
||||
/**
|
||||
* A null option.
|
||||
*/
|
||||
public static final Option NULL = new Option("null", 0);
|
||||
|
||||
/**
|
||||
* The option name.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* The index.
|
||||
*/
|
||||
private final int index;
|
||||
|
||||
/**
|
||||
* The option handler.
|
||||
*/
|
||||
private OptionHandler handler;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Interaction} {@code Object}.
|
||||
* @param name The name.
|
||||
* @param index The index.
|
||||
*/
|
||||
public Option(String name, int index) {
|
||||
this.name = name;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default option handler for the given name.
|
||||
* @param node The node type that has this option.
|
||||
* @param nodeId The id of the node.
|
||||
* @param name The name of the option.
|
||||
* @return The default option handler for this option.
|
||||
*/
|
||||
public static OptionHandler defaultHandler(Node node, int nodeId, String name) {
|
||||
name = name.toLowerCase();
|
||||
if (node instanceof NPC) {
|
||||
return NPCDefinition.getOptionHandler(nodeId, name);
|
||||
}
|
||||
if (node instanceof Scenery) {
|
||||
return SceneryDefinition.getOptionHandler(nodeId, name);
|
||||
}
|
||||
if (node instanceof Item) {
|
||||
return ItemDefinition.getOptionHandler(nodeId, name);
|
||||
}
|
||||
System.out.println("Unhandled node type " + node + "!");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name.
|
||||
* @return The name.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index.
|
||||
* @return The index.
|
||||
*/
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the handler.
|
||||
* @return The handler.
|
||||
*/
|
||||
public OptionHandler getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the handler.
|
||||
* @param handler The handler to set.
|
||||
* @return This option instance.
|
||||
*/
|
||||
public Option setHandler(OptionHandler handler) {
|
||||
this.handler = handler;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package core.game.interaction;
|
||||
|
||||
import core.cache.def.impl.SceneryDefinition;
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.tools.SystemLogger;
|
||||
import core.game.world.map.Location;
|
||||
import core.plugin.Plugin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handles an interaction option.
|
||||
* @author Emperor
|
||||
*/
|
||||
public abstract class OptionHandler implements Plugin<Object> {
|
||||
|
||||
/**
|
||||
* Handles the interaction option.
|
||||
* @param player The player who used the option.
|
||||
* @param node The node the player selected an option on.
|
||||
* @param option The option selected.
|
||||
* @return {@code True} if successful.
|
||||
*/
|
||||
public abstract boolean handle(Player player, Node node, String option);
|
||||
|
||||
/**
|
||||
* Checks if the option should be handled after 1 game tick.
|
||||
* @param player The player.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public boolean isDelayed(Player player) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if it needs a walk..
|
||||
* @param node the node.
|
||||
* @return true if so.
|
||||
*/
|
||||
public boolean isWalk(final Player player, final Node node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the walk.
|
||||
* @return if a walk is required.
|
||||
*/
|
||||
public boolean isWalk() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the custom destination for the node.
|
||||
* @param n The moving node.
|
||||
* @param node The node to walk to.
|
||||
* @return The custom destination, or {@code null} if we should use the
|
||||
* default destination.
|
||||
*/
|
||||
public Location getDestination(Node n, Node node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the valid children for the wrapper id.
|
||||
* @param wrapper the wrapper id.
|
||||
* @return the valid children.
|
||||
*/
|
||||
public int[] getValidChildren(int wrapper) {
|
||||
final SceneryDefinition definition = SceneryDefinition.forId(wrapper);
|
||||
final List<Integer> list = new ArrayList<>(20);
|
||||
if (definition.getChildrenIds() == null) {
|
||||
SystemLogger.logErr(this.getClass(), "Null child wrapper in option handler wrapperId=" + wrapper);
|
||||
return new int[] { wrapper };
|
||||
}
|
||||
for (int child : definition.getChildrenIds()) {
|
||||
if (child != -1 && !list.contains(child)) {
|
||||
list.add(child);
|
||||
}
|
||||
}
|
||||
int[] array = new int[list.size()];
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
array[i] = list.get(i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fireEvent(String identifier, Object... args) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package core.game.interaction;
|
||||
|
||||
import core.game.interaction.NodeUsageEvent;
|
||||
import core.game.interaction.Option;
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
import core.plugin.Plugin;
|
||||
|
||||
public abstract class PluginInteraction implements Plugin<Object> {
|
||||
int[] ids;
|
||||
Item item;
|
||||
public boolean handle(Player player, Node node){
|
||||
return false;
|
||||
};
|
||||
public boolean handle(Player player, NodeUsageEvent event){
|
||||
return false;
|
||||
};
|
||||
public boolean handle(Player player, NPC npc, Option option){
|
||||
return false;
|
||||
}
|
||||
public boolean handle(Player player, Item item, Option option){return false;}
|
||||
public PluginInteraction(int... ids){
|
||||
this.ids = ids;
|
||||
}
|
||||
public PluginInteraction(Item item){this.item = item;}
|
||||
|
||||
public void setIds(int[] ids) {
|
||||
this.ids = ids;
|
||||
}
|
||||
|
||||
public void setItem(Item item) {
|
||||
this.item = item;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package core.game.interaction;
|
||||
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
import core.game.node.scenery.Scenery;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class PluginInteractionManager {
|
||||
private static final HashMap<Integer, PluginInteraction> npcInteractions = new HashMap<>();
|
||||
private static final HashMap<Integer, PluginInteraction> objectInteractions = new HashMap<>();
|
||||
private static final HashMap<Integer, PluginInteraction> useWithInteractions = new HashMap<>();
|
||||
private static final HashMap<Integer, PluginInteraction> groundItemInteractions = new HashMap<>();
|
||||
|
||||
public static void register(PluginInteraction interaction, InteractionType type){
|
||||
switch(type){
|
||||
case OBJECT:
|
||||
for(int i = 0; i < interaction.ids.length; i++){
|
||||
objectInteractions.putIfAbsent(interaction.ids[i],interaction);
|
||||
}
|
||||
break;
|
||||
case USEWITH:
|
||||
for(int i = 0; i < interaction.ids.length; i++){
|
||||
useWithInteractions.putIfAbsent(interaction.ids[i],interaction);
|
||||
}
|
||||
break;
|
||||
case NPC:
|
||||
for(int i = 0; i < interaction.ids.length; i++){
|
||||
npcInteractions.putIfAbsent(interaction.ids[i],interaction);
|
||||
}
|
||||
break;
|
||||
case ITEM:
|
||||
for(int i = 0; i < interaction.ids.length; i++){
|
||||
groundItemInteractions.putIfAbsent(interaction.ids[i],interaction);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean handle(Player player, Scenery object){
|
||||
PluginInteraction i = objectInteractions.get(object.getId());
|
||||
if(i == null) {
|
||||
return false;
|
||||
} else {
|
||||
return i.handle(player,object);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean handle(Player player, NodeUsageEvent event){
|
||||
PluginInteraction i = useWithInteractions.get(event.getUsed().asItem().getId());
|
||||
if(i == null) {
|
||||
return false;
|
||||
} else {
|
||||
return i.handle(player,event);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean handle(Player player, NPC npc, Option option){
|
||||
PluginInteraction i = npcInteractions.get(npc.getId());
|
||||
if(i == null) {
|
||||
return false;
|
||||
} else {
|
||||
return i.handle(player,npc,option);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean handle(Player player, Item item, Option option){
|
||||
PluginInteraction i = groundItemInteractions.get(item.getId());
|
||||
if(i == null){
|
||||
return false;
|
||||
} else {
|
||||
return i.handle(player,item,option);
|
||||
}
|
||||
}
|
||||
|
||||
public enum InteractionType{
|
||||
NPC,
|
||||
OBJECT,
|
||||
USEWITH,
|
||||
ITEM;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package core.game.interaction;
|
||||
|
||||
import core.game.node.entity.player.Player;
|
||||
|
||||
/**
|
||||
* Stub class for special ground item interactions
|
||||
* @author ceik
|
||||
*/
|
||||
public class SpecialGroundInteraction {
|
||||
public void handle(Player player, Option option){
|
||||
}
|
||||
public void configure(){
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package core.game.interaction;
|
||||
|
||||
import content.region.asgarnia.portsarim.handlers.AhabBeerInteraction;
|
||||
import core.game.node.item.GroundItem;
|
||||
import core.game.node.item.GroundItemManager;
|
||||
import core.game.world.map.Location;
|
||||
|
||||
/**
|
||||
* Handles interactions for special ground items
|
||||
* @author ceik
|
||||
*/
|
||||
public enum SpecialGroundItems {
|
||||
//Ahab's beer
|
||||
AHAB_BEER(1917,new Location(3049,3257,0), new AhabBeerInteraction());
|
||||
|
||||
private int itemid;
|
||||
private Location location;
|
||||
private SpecialGroundInteraction inter;
|
||||
|
||||
SpecialGroundItems(int itemId, Location location, SpecialGroundInteraction inter){
|
||||
this.itemid = itemId;
|
||||
this.location = location;
|
||||
this.inter = inter;
|
||||
}
|
||||
|
||||
public SpecialGroundInteraction getInteraction() { return inter;}
|
||||
public int getItemid() {return itemid;}
|
||||
public Location getLocation() {return location;}
|
||||
public GroundItem asGroundItem(){return GroundItemManager.get(itemid,location,null);}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package core.game.interaction;
|
||||
|
||||
import core.game.event.UseWithEvent;
|
||||
import core.cache.def.impl.SceneryDefinition;
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.impl.PulseType;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
import core.game.node.scenery.Scenery;
|
||||
import core.tools.SystemLogger;
|
||||
import core.game.system.task.Pulse;
|
||||
import core.game.world.map.Location;
|
||||
import core.game.world.update.flag.player.FaceLocationFlag;
|
||||
import core.plugin.Plugin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Handles the "use {@code node a} with {@code node b}" option.
|
||||
*
|
||||
* @author Emperor
|
||||
*/
|
||||
public abstract class UseWithHandler implements Plugin<Object> {
|
||||
|
||||
/**
|
||||
* The item type.
|
||||
*/
|
||||
public static final int ITEM_TYPE = 0;
|
||||
|
||||
/**
|
||||
* The NPC type.
|
||||
*/
|
||||
public static final int NPC_TYPE = 1;
|
||||
|
||||
/**
|
||||
* The object type.
|
||||
*/
|
||||
public static final int OBJECT_TYPE = 2;
|
||||
|
||||
/**
|
||||
* The player type.
|
||||
*/
|
||||
public static final int PLAYER_TYPE = 3;
|
||||
|
||||
/**
|
||||
* The handlers.
|
||||
*/
|
||||
private static final Map<Integer, List<UseWithHandler>> HANDLERS = new HashMap<>();
|
||||
|
||||
/**
|
||||
* The allowed node ids.
|
||||
*/
|
||||
private int[] allowedNodes;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code UseWithHandler.java} {@code Object}.
|
||||
*
|
||||
* @param allowedNodes
|
||||
*/
|
||||
public UseWithHandler(int... allowedNodes) {
|
||||
this.allowedNodes = allowedNodes;
|
||||
}
|
||||
|
||||
public UseWithHandler(ArrayList<Integer> allowedNodes){
|
||||
this.allowedNodes = allowedNodes.stream().mapToInt(i -> i).toArray();
|
||||
}
|
||||
|
||||
public void setAllowedNodes(ArrayList<Integer> allowedNodes){
|
||||
this.allowedNodes = allowedNodes.stream().mapToInt(i -> i).toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a handler.
|
||||
*
|
||||
* @param id The node id.
|
||||
* @param type The node type (0=item, 1=NPC, 2=object, 3=player).
|
||||
* @param handler The handler.
|
||||
*/
|
||||
public static void addHandler(int id, int type, UseWithHandler handler) {
|
||||
int key = id | type << 16;
|
||||
List<UseWithHandler> handlers = HANDLERS.get(key);
|
||||
if (handlers == null) {
|
||||
HANDLERS.put(key, handlers = new ArrayList<>(20));
|
||||
}
|
||||
if (type == PLAYER_TYPE) {
|
||||
if (handler.allowedNodes == null) {
|
||||
handler.allowedNodes = new int[]{id};
|
||||
} else {
|
||||
int[] array = handler.allowedNodes;
|
||||
handler.allowedNodes = new int[handler.allowedNodes.length + 1];
|
||||
System.arraycopy(array, 0, handler.allowedNodes, 0, array.length);
|
||||
handler.allowedNodes[handler.allowedNodes.length - 1] = id;
|
||||
}
|
||||
}
|
||||
handlers.add(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the event.
|
||||
*
|
||||
* @param event The event.
|
||||
*/
|
||||
public static void run(final NodeUsageEvent event) {
|
||||
try {
|
||||
if (event.getPlayer() != null) {
|
||||
event.getPlayer().getInterfaceManager().close();
|
||||
}
|
||||
Node n = event.getUsedWith();
|
||||
List<UseWithHandler> handler = null;
|
||||
if (n instanceof Item) {
|
||||
handler = HANDLERS.get(((Item) event.getUsed()).getId());// fixed.
|
||||
if (handler == null) {
|
||||
handler = HANDLERS.get(((Item) event.getUsedWith()).getId());
|
||||
}
|
||||
} else if (n instanceof NPC) {
|
||||
handler = HANDLERS.get(((NPC) n).getId() | NPC_TYPE << 16);
|
||||
} else if (n instanceof Scenery) {
|
||||
handler = HANDLERS.get(((Scenery) n).getId() | OBJECT_TYPE << 16);
|
||||
} else if (n instanceof Player) {
|
||||
handler = HANDLERS.get(((Item) event.getUsed()).getId() | PLAYER_TYPE << 16);
|
||||
} else {
|
||||
handler = HANDLERS.get(((NPC) n).getId() | NPC_TYPE << 16);
|
||||
}
|
||||
if (handler == null) {
|
||||
if (n instanceof Item && !(event.getUsed() instanceof Player)) {
|
||||
event.getPlayer().getPulseManager().runUnhandledAction(event.getPlayer(), PulseType.STANDARD);
|
||||
} else {
|
||||
event.getPlayer().getPulseManager().run(new MovementPulse(event.getPlayer(), event.getUsedWith()) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
event.getPlayer().debug("Unhandled use with interaction: item used: " + event.getUsed() + " with: " + event.getUsedWith());
|
||||
event.getPlayer().getPacketDispatch().sendMessage("Nothing interesting happens.");
|
||||
return true;
|
||||
}
|
||||
}, PulseType.STANDARD);
|
||||
}
|
||||
return;
|
||||
}
|
||||
final List<UseWithHandler> handlers = handler;
|
||||
if (n instanceof Item && !(event.getUsed() instanceof Player)) {
|
||||
event.getPlayer().getPulseManager().run(new Pulse(1, event.getPlayer(), event.getUsed(), event.getUsedWith()) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
event.getPlayer().dispatch(new UseWithEvent(event.getUsed().getId(), event.getUsedWith().getId()));
|
||||
boolean handled = false;
|
||||
if (event.getPlayer() != null) {
|
||||
event.getPlayer().getInterfaceManager().close();
|
||||
}
|
||||
for (UseWithHandler h : handlers) {
|
||||
if (!h.nodeAllowed(((Item) event.getUsedWith()).getId()) && !h.nodeAllowed(event.getUsedItem().getId()) || !h.handle(event)) {// fixed,
|
||||
continue;
|
||||
}
|
||||
event.getPlayer().debug("Handler=" + h.getClass().getSimpleName() + ", used item=" + event.getUsedItem() + ", used with=" + event.getUsedWith());
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
if (!handled) {
|
||||
event.getPlayer().debug("Handler=none, used item=" + event.getUsedItem());
|
||||
event.getPlayer().debug("used with=" + event.getUsedWith());
|
||||
event.getPlayer().getPacketDispatch().sendMessage("Nothing interesting happens.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}, PulseType.STANDARD);
|
||||
return;
|
||||
}
|
||||
event.getPlayer().getPulseManager().run(new MovementPulse(event.getPlayer(), event.getUsedWith(), handler.get(0)) {
|
||||
@Override
|
||||
public boolean pulse() {
|
||||
event.getPlayer().dispatch(new UseWithEvent(event.getUsed().getId(), event.getUsedWith().getId()));
|
||||
event.getPlayer().faceLocation(FaceLocationFlag.getFaceLocation(event.getPlayer(), event.getUsedWith()));
|
||||
boolean handled = false;
|
||||
Item used = (Item) event.getUsed();
|
||||
for (UseWithHandler h : handlers) {
|
||||
if ((used != null && !h.nodeAllowed(used.getId())) || !h.handle(event)) {
|
||||
continue;
|
||||
}
|
||||
event.getPlayer().debug("Handler=" + h.getClass().getSimpleName() + ", used item=" + event.getUsedItem() + ", used with=" + event.getUsedWith());
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
if (!handled) {
|
||||
event.getPlayer().debug("Handler=none, used item=" + event.getUsedItem() + ", used with=" + event.getUsedWith());
|
||||
event.getPlayer().getPacketDispatch().sendMessage("Nothing interesting happens.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}, PulseType.STANDARD);
|
||||
} catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fireEvent(String identifier, Object... args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the valid children for the wrapper id.
|
||||
*
|
||||
* @param wrapper the wrapper id.
|
||||
* @return the valid children.
|
||||
*/
|
||||
public int[] getValidChildren(int wrapper) {
|
||||
final SceneryDefinition definition = SceneryDefinition.forId(wrapper);
|
||||
final List<Integer> list = new ArrayList<>(20);
|
||||
if (definition.getChildrenIds() == null) {
|
||||
SystemLogger.logErr(this.getClass(), "Null child wrapper in option handler wrapperId=" + wrapper);
|
||||
return new int[]{wrapper};
|
||||
}
|
||||
for (int child : definition.getChildrenIds()) {
|
||||
if (child != -1 && !list.contains(child)) {
|
||||
list.add(child);
|
||||
}
|
||||
}
|
||||
int[] array = new int[list.size()];
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
array[i] = list.get(i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to get the destination to go to, leave null to go to proper
|
||||
* one.
|
||||
*
|
||||
* @param player the player.
|
||||
* @return the location.
|
||||
*/
|
||||
public Location getDestination(Player player, Node with) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the node is allowed to be used with the base node.
|
||||
*
|
||||
* @param nodeId The node id.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public boolean nodeAllowed(int nodeId) {
|
||||
if (isDynamic()) {
|
||||
return true;
|
||||
}
|
||||
for (int id : allowedNodes) {
|
||||
if (nodeId == id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the interaction option.
|
||||
*
|
||||
* @param event The node usage event.
|
||||
* @return {@code True} if successful.
|
||||
*/
|
||||
public abstract boolean handle(NodeUsageEvent event);
|
||||
|
||||
/**
|
||||
* Checks if the handler excepts all dynamic items.
|
||||
*
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public boolean isDynamic() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user