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:
Ceikry
2023-01-26 13:59:28 +00:00
committed by Ryan
parent e4b799c44a
commit a100affda2
3156 changed files with 24205 additions and 30585 deletions
@@ -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;
}
}