Implement Authentic Interaction Subsystem
Implemented authentic script/interaction queues This does now mean we have a total of 3 interaction systems, but this additional system is necessary to fix certain categories of bug and implement some authentic features Converted mining to new system Converted fishing to new system Converted woodcutting to new system Provided an example of soft-queued scripts with GrandTreePodListener Implemented tick-eating (it is now possible to eat a shark, drink a potion, and eat a karambwan all on the same tick) Can now eat and drop items while stunned
This commit is contained in:
@@ -53,6 +53,9 @@ import core.game.interaction.InteractionListeners
|
||||
import content.global.handlers.iface.ge.StockMarket
|
||||
import content.global.skill.slayer.SlayerManager
|
||||
import core.game.activity.Cutscene
|
||||
import core.game.interaction.Clocks
|
||||
import core.game.interaction.QueueStrength
|
||||
import core.game.interaction.QueuedScript
|
||||
import core.game.node.entity.player.info.LogType
|
||||
import core.game.node.entity.player.info.PlayerMonitor
|
||||
import core.tools.SystemLogger
|
||||
@@ -60,7 +63,9 @@ import core.game.system.config.ItemConfigParser
|
||||
import core.game.system.config.ServerConfigParser
|
||||
import core.game.world.GameWorld
|
||||
import core.game.world.GameWorld.Pulser
|
||||
import core.game.world.map.path.ProjectilePathfinder
|
||||
import core.game.world.repository.Repository
|
||||
import core.tools.tick
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
/**
|
||||
@@ -2261,6 +2266,97 @@ fun addDialogueAction(player: Player, action: core.game.dialogue.DialogueAction)
|
||||
player.dialogueInterpreter.addAction(action)
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by content handlers to check if the entity is done moving yet
|
||||
*/
|
||||
fun finishedMoving(entity: Entity) : Boolean {
|
||||
return entity.clocks[Clocks.MOVEMENT] < GameWorld.ticks
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delay the execution of the currently running script
|
||||
*/
|
||||
fun delayScript(entity: Entity, ticks: Int): Boolean {
|
||||
entity.scripts.getActiveScript()?.let { it.nextExecution = GameWorld.ticks + ticks }
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the global delay for the entity, pausing execution of all queues/scripts until passed.
|
||||
*/
|
||||
fun delayEntity(entity: Entity, ticks: Int) {
|
||||
entity.scripts.delay = GameWorld.ticks + ticks
|
||||
lock(entity, 5) //TODO: REMOVE WHEN EVERYTHING IMPORTANT USES PROPER QUEUES - THIS IS INCORRECT BEHAVIOR
|
||||
}
|
||||
|
||||
fun apRange(entity: Entity, apRange: Int) {
|
||||
entity.scripts.apRange = apRange
|
||||
entity.scripts.apRangeCalled = true
|
||||
}
|
||||
|
||||
fun hasLineOfSight(entity: Entity, target: Node) : Boolean {
|
||||
return ProjectilePathfinder.find(entity, target).isSuccessful
|
||||
}
|
||||
|
||||
fun animationFinished(entity: Entity) : Boolean {
|
||||
return entity.clocks[Clocks.ANIMATION_END] < GameWorld.ticks
|
||||
}
|
||||
|
||||
fun clearScripts(entity: Entity) : Boolean {
|
||||
entity.scripts.reset()
|
||||
return true
|
||||
}
|
||||
|
||||
fun restartScript(entity: Entity) : Boolean {
|
||||
if (entity.scripts.getActiveScript()?.persist != true) {
|
||||
SystemLogger.logErr(entity.scripts.getActiveScript()!!::class.java, "Tried to call restartScript on a non-persistent script! Either use stopExecuting() or make the script persistent.")
|
||||
return clearScripts(entity)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun keepRunning(entity: Entity) : Boolean {
|
||||
entity.scripts.getActiveScript()?.nextExecution = getWorldTicks() + 1
|
||||
return false
|
||||
}
|
||||
|
||||
fun stopExecuting(entity: Entity) : Boolean {
|
||||
if (entity.scripts.getActiveScript()?.persist == true) {
|
||||
SystemLogger.logErr(entity.scripts.getActiveScript()!!::class.java, "Tried to call stopExecuting() on a persistent script! To halt execution of a persistent script, you MUST call clearScripts()!")
|
||||
return clearScripts(entity)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun queueScript(entity: Entity, delay: Int = 1, strength: QueueStrength = QueueStrength.WEAK, persist: Boolean = false, script: (stage: Int) -> Boolean) {
|
||||
val s = QueuedScript(script, strength, persist)
|
||||
s.nextExecution = getWorldTicks() + delay
|
||||
entity.scripts.addToQueue(s, strength)
|
||||
}
|
||||
|
||||
fun delayAttack(entity: Entity, ticks: Int) {
|
||||
entity.properties.combatPulse.delayNextAttack(3)
|
||||
entity.clocks[Clocks.NEXT_ATTACK] = getWorldTicks() + ticks
|
||||
}
|
||||
|
||||
fun stun(entity: Entity, ticks: Int) {
|
||||
entity.walkingQueue.reset()
|
||||
entity.pulseManager.clear()
|
||||
entity.locks.lockMovement(ticks)
|
||||
entity.clocks[Clocks.STUN] = getWorldTicks() + ticks
|
||||
entity.graphics(Graphics(80, 96))
|
||||
if (entity is Player) {
|
||||
entity.audioManager.send(Audio(2727, 1, 0))
|
||||
entity.animate(Animation(424, Animator.Priority.VERY_HIGH))
|
||||
sendMessage(entity, "You have been stunned!")
|
||||
}
|
||||
}
|
||||
|
||||
fun isStunned(entity: Entity) : Boolean {
|
||||
return entity.clocks[Clocks.STUN] >= getWorldTicks()
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies prayer points by value
|
||||
* @param player the player to modify prayer points
|
||||
|
||||
@@ -5,16 +5,13 @@ import core.cache.Cache;
|
||||
import core.cache.def.Definition;
|
||||
import core.cache.misc.buffer.ByteBufferUtils;
|
||||
import core.game.container.Container;
|
||||
import core.game.global.action.DropItemHandler;
|
||||
import core.game.interaction.OptionHandler;
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.entity.skill.Skills;
|
||||
import core.game.node.item.Item;
|
||||
import core.game.node.item.ItemPlugin;
|
||||
import core.net.packet.PacketRepository;
|
||||
import core.net.packet.out.WeightUpdate;
|
||||
import core.plugin.Plugin;
|
||||
import core.tools.StringUtils;
|
||||
import core.tools.SystemLogger;
|
||||
import core.game.system.config.ItemConfigParser;
|
||||
@@ -259,32 +256,6 @@ public class ItemDefinition extends Definition<Item> {
|
||||
options = new String[] { null, null, null, null, "drop" };
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the default option handlers.
|
||||
*/
|
||||
static {
|
||||
// TODO: Move this crap in a plugin.
|
||||
OptionHandler handler = new OptionHandler() {
|
||||
@Override
|
||||
public Plugin<Object> newInstance(Object arg) throws Throwable {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(final Player player, Node node, String option) {
|
||||
return DropItemHandler.handle(player, node, option);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWalk() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
setOptionHandler("destroy", handler);
|
||||
setOptionHandler("dissolve", handler);
|
||||
setOptionHandler("drop", handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the item definitions.
|
||||
*/
|
||||
|
||||
@@ -43,7 +43,7 @@ class CombatBot(location: Location) : AIPlayer(location) {
|
||||
this.lock(3)
|
||||
//this.animate(new Animation(829));
|
||||
val food = inventory.getItem(foodItem)
|
||||
var consumable: Consumable? = Consumables.getConsumableById(food.id)
|
||||
var consumable: Consumable? = Consumables.getConsumableById(food.id)?.consumable
|
||||
if (consumable == null) {
|
||||
consumable = Food(IntArray(food.id), HealingEffect(1))
|
||||
}
|
||||
|
||||
@@ -133,10 +133,10 @@ public class PvMBots extends AIPlayer {
|
||||
//this.animate(new Animation(829));
|
||||
Item food = this.getInventory().getItem(foodItem);
|
||||
|
||||
Consumable consumable = Consumables.getConsumableById(food.getId());
|
||||
Consumable consumable = Consumables.getConsumableById(food.getId()).getConsumable();
|
||||
|
||||
if (consumable == null) {
|
||||
consumable = new Food(new int[] {food.getId()}, new HealingEffect(1));
|
||||
return;
|
||||
}
|
||||
|
||||
consumable.consume(food, this);
|
||||
|
||||
@@ -653,7 +653,7 @@ class ScriptAPI(private val bot: Player) {
|
||||
bot.lock(3)
|
||||
//this.animate(new Animation(829));
|
||||
val food = bot.inventory.getItem(foodItem)
|
||||
var consumable: Consumable? = Consumables.getConsumableById(foodId)
|
||||
var consumable: Consumable? = Consumables.getConsumableById(foodId)?.consumable
|
||||
if (consumable == null) {
|
||||
consumable = Food(intArrayOf(food.id), HealingEffect(1))
|
||||
}
|
||||
@@ -673,7 +673,7 @@ class ScriptAPI(private val bot: Player) {
|
||||
bot.lock(3)
|
||||
//this.animate(new Animation(829));
|
||||
val food = bot.inventory.getItem(foodItem)
|
||||
var consumable: Consumable? = Consumables.getConsumableById(foodId)
|
||||
var consumable: Consumable? = Consumables.getConsumableById(foodId)?.consumable
|
||||
if (consumable == null) {
|
||||
consumable = Food(intArrayOf(foodId), HealingEffect(1))
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class Cake extends Food {
|
||||
player.getInventory().remove(item);
|
||||
}
|
||||
final int initialLifePoints = player.getSkills().getLifepoints();
|
||||
Consumables.getConsumableById(item.getId()).effect.activate(player);
|
||||
Consumables.getConsumableById(item.getId()).getConsumable().effect.activate(player);
|
||||
sendMessages(player, initialLifePoints, item, messages);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import core.plugin.Plugin;
|
||||
/**
|
||||
* Represents any item that has a consumption option such as 'Eat' or 'Drink'.
|
||||
*/
|
||||
public abstract class Consumable implements Plugin<Object> {
|
||||
public abstract class Consumable {
|
||||
|
||||
/**
|
||||
* Represents the item IDs of all the variants of a consumable where the last one is often the empty container, if it has any.
|
||||
@@ -58,7 +58,7 @@ public abstract class Consumable implements Plugin<Object> {
|
||||
addItem(player, nextItemId, 1, Container.INVENTORY);
|
||||
}
|
||||
final int initialLifePoints = player.getSkills().getLifepoints();
|
||||
Consumables.getConsumableById(item.getId()).effect.activate(player);
|
||||
Consumables.getConsumableById(item.getId()).getConsumable().effect.activate(player);
|
||||
sendMessages(player, initialLifePoints, item, messages);
|
||||
}
|
||||
|
||||
@@ -100,17 +100,6 @@ public abstract class Consumable implements Plugin<Object> {
|
||||
return item.getName().replace("(4)", "").replace("(3)", "").replace("(2)", "").replace("(1)", "").trim().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Plugin<Object> newInstance(Object arg) throws Throwable {
|
||||
Consumables.add(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fireEvent(String identifier, Object... args) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getHealthEffectValue(Player player) {
|
||||
return effect.getHealthEffectValue(player);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public class Potion extends Drink {
|
||||
}
|
||||
|
||||
final int initialLifePoints = player.getSkills().getLifepoints();
|
||||
Consumables.getConsumableById(item.getId()).effect.activate(player);
|
||||
Consumables.getConsumableById(item.getId()).getConsumable().effect.activate(player);
|
||||
if (messages.length == 0) {
|
||||
sendDefaultMessages(player, item);
|
||||
} else {
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
package core.game.global.action;
|
||||
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.entity.player.info.login.PlayerParser;
|
||||
import core.game.node.entity.player.link.audio.Audio;
|
||||
import core.game.node.item.GroundItemManager;
|
||||
import core.game.node.item.Item;
|
||||
import core.game.node.entity.combat.graves.GraveController;
|
||||
import core.tools.SystemLogger;
|
||||
import core.game.system.config.ItemConfigParser;
|
||||
import core.game.world.GameWorld;
|
||||
|
||||
/**
|
||||
* Handles the dropping of an item.
|
||||
* @author Vexia
|
||||
*/
|
||||
public final class DropItemHandler {
|
||||
|
||||
/**
|
||||
* Handles the droping of an item.
|
||||
* @param player the player.
|
||||
* @param node the node.
|
||||
* @param option the option.
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public static boolean handle(final Player player, Node node, String option) {
|
||||
Item item = (Item) node;
|
||||
if (item.getSlot() == -1) {
|
||||
player.getPacketDispatch().sendMessage("Invalid slot!");
|
||||
return false;
|
||||
}
|
||||
switch (option) {
|
||||
case "drop":
|
||||
case "destroy":
|
||||
case "dissolve":
|
||||
if (!player.getInterfaceManager().close()) {
|
||||
return true;
|
||||
}
|
||||
player.getDialogueInterpreter().close();
|
||||
player.getPulseManager().clear();
|
||||
if (option.equalsIgnoreCase("destroy") || option.equalsIgnoreCase("dissolve") || (boolean) item.getDefinition().getHandlers().getOrDefault(ItemConfigParser.DESTROY,false)) {
|
||||
player.getDialogueInterpreter().open(9878, item);
|
||||
return true;
|
||||
}
|
||||
if (GraveController.hasGraveAt(player.getLocation())) {
|
||||
player.sendMessage("You cannot drop items on top of graves!");
|
||||
return false;
|
||||
}
|
||||
if (player.getAttribute("equipLock:" + item.getId(), 0) > GameWorld.getTicks()) {
|
||||
SystemLogger.logAlert(DropItemHandler.class, player + ", tried to do the drop & equip dupe.");
|
||||
return true;
|
||||
}
|
||||
if (player.getInventory().replace(null, item.getSlot()) == item) {
|
||||
item = item.getDropItem();
|
||||
player.getAudioManager().send(new Audio(item.getId() == 995 ? 10 : 2739, 1, 0));//2739 ACTUAL DROP SOUND
|
||||
GroundItemManager.create(item, player.getLocation(), player);
|
||||
PlayerParser.save(player);
|
||||
}
|
||||
player.setAttribute("droppedItem:" + item.getId(), GameWorld.getTicks() + 2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops an item.
|
||||
* @param player the player.
|
||||
* @param item the item.
|
||||
* @return
|
||||
*/
|
||||
public static boolean drop(Player player, Item item) {
|
||||
return handle(player, item, item.getDefinition().hasDestroyAction() ? "destroy" : "drop");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package core.game.global.action
|
||||
|
||||
import core.api.*
|
||||
import core.game.interaction.IntType
|
||||
import core.game.interaction.InteractionListener
|
||||
import core.game.interaction.QueueStrength
|
||||
import core.game.node.Node
|
||||
import core.game.node.entity.combat.graves.GraveController
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.entity.player.info.login.PlayerParser
|
||||
import core.game.node.entity.player.link.audio.Audio
|
||||
import core.game.node.item.GroundItemManager
|
||||
import core.game.node.item.Item
|
||||
import core.game.system.config.ItemConfigParser
|
||||
|
||||
class DropListener : InteractionListener {
|
||||
override fun defineListeners() {
|
||||
on(IntType.ITEM, "drop", "destroy", "dissolve", handler = ::handleDropAction)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic fun drop(player: Player, item: Item) : Boolean {
|
||||
return handleDropAction(player, item)
|
||||
}
|
||||
private fun handleDropAction(player: Player, node: Node) : Boolean {
|
||||
val option = getUsedOption(player)
|
||||
var item = node as? Item ?: return false
|
||||
if (option == "drop") {
|
||||
if (GraveController.hasGraveAt(player.location)) {
|
||||
sendMessage(player, "You cannot drop items on top of graves!")
|
||||
return false
|
||||
}
|
||||
if (getAttribute(player, "equipLock:${node.id}", 0 ) > getWorldTicks())
|
||||
return false
|
||||
|
||||
queueScript (player, strength = QueueStrength.SOFT) {
|
||||
if (player.inventory.replace(null, item.slot) != item) return@queueScript stopExecuting(player)
|
||||
item = item.dropItem
|
||||
player.audioManager.send(Audio(if (item.id == 995) 10 else 2739, 1, 0))
|
||||
GroundItemManager.create(item, player.location, player)
|
||||
setAttribute(player, "droppedItem:${item.id}", getWorldTicks() + 2)
|
||||
PlayerParser.save(player)
|
||||
return@queueScript stopExecuting(player)
|
||||
}
|
||||
} else if (option == "destroy" || option == "dissolve" || item.definition.handlers.getOrDefault(ItemConfigParser.DESTROY, false) as Boolean) {
|
||||
player.dialogueInterpreter.open(9878, item)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package core.game.interaction
|
||||
|
||||
object Clocks {
|
||||
@JvmStatic val MOVEMENT = 0
|
||||
@JvmStatic val ANIMATION_END = 1
|
||||
@JvmStatic val NEXT_EAT = 2
|
||||
@JvmStatic val NEXT_CONSUME = 3
|
||||
@JvmStatic val NEXT_DRINK = 4
|
||||
@JvmStatic val NEXT_ATTACK = 5
|
||||
@JvmStatic val STUN = 6
|
||||
}
|
||||
+2
-2
@@ -20,7 +20,7 @@ import core.game.world.GameWorld;
|
||||
* Handles interaction between nodes.
|
||||
* @author Emperor
|
||||
*/
|
||||
public class Interaction {
|
||||
public class InteractPlugin {
|
||||
|
||||
/**
|
||||
* The current options.
|
||||
@@ -41,7 +41,7 @@ public class Interaction {
|
||||
* Constructs a new {@code Interaction} {@code Object}.
|
||||
* @param node The node reference.
|
||||
*/
|
||||
public Interaction(Node node) {
|
||||
public InteractPlugin(Node node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ interface InteractionListener : ContentInterface{
|
||||
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){
|
||||
@Deprecated("Don't use") 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){
|
||||
@@ -82,5 +82,16 @@ interface InteractionListener : ContentInterface{
|
||||
InteractionListeners.instantClasses.add(name)
|
||||
}
|
||||
|
||||
fun defineInteraction(type: IntType, ids: IntArray, vararg options: String, persistent: Boolean = false, allowedDistance: Int = 1, handler: (player: Player, node: Node, state: Int) -> Boolean) {
|
||||
InteractionListeners.addMetadata(ids, type, options, InteractionMetadata(handler, allowedDistance, persistent))
|
||||
}
|
||||
|
||||
fun defineInteraction(type: IntType, vararg options: String, persist: Boolean = false, allowedDistance: Int = 1, handler: (player: Player, node: Node, state: Int) -> Boolean) {
|
||||
InteractionListeners.addGenericMetadata(options, type, InteractionMetadata(handler, allowedDistance, persist))
|
||||
}
|
||||
|
||||
data class InteractionMetadata(val handler: (player: Player, node: Node, state: Int) -> Boolean, val distance: Int, val persist: Boolean)
|
||||
data class UseWithMetadata(val handler: (player: Player, used: Node, with: Node, state: Int) -> Boolean, val distance: Int, val persist: Boolean)
|
||||
|
||||
fun defineListeners()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package core.game.interaction
|
||||
|
||||
import core.api.forceWalk
|
||||
import core.api.queueScript
|
||||
import core.game.event.InteractionEvent
|
||||
import core.game.event.UseWithEvent
|
||||
import core.game.node.Node
|
||||
@@ -14,6 +16,8 @@ object InteractionListeners {
|
||||
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)
|
||||
private val interactions = HashMap<String, InteractionListener.InteractionMetadata>()
|
||||
private val useWithInteractions = HashMap<String, InteractionListener.UseWithMetadata>()
|
||||
val instantClasses = HashSet<String>()
|
||||
|
||||
@JvmStatic
|
||||
@@ -232,10 +236,25 @@ object InteractionListeners {
|
||||
|
||||
if(player.locks.isInteractionLocked) return false
|
||||
|
||||
val method = get(id,type.ordinal,option) ?: get(option,type.ordinal) ?: return false
|
||||
val method = get(id,type.ordinal,option) ?: get(option,type.ordinal)
|
||||
|
||||
player.setAttribute("interact:option", option.lowercase())
|
||||
player.dispatch(InteractionEvent(node, option.toLowerCase()))
|
||||
|
||||
if (method == null) {
|
||||
val inter = interactions["${type.ordinal}:$id:${option.lowercase()}"] ?: interactions["${type.ordinal}:${option.lowercase()}"] ?: return false
|
||||
val script = Interaction(inter.handler, inter.distance, inter.persist)
|
||||
player.scripts.setInteractionScript(node, script)
|
||||
player.pulseManager.run(object : MovementPulse(player, node, flag) {
|
||||
override fun pulse(): Boolean {
|
||||
return true
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
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 >.>
|
||||
{
|
||||
@@ -250,14 +269,12 @@ object InteractionListeners {
|
||||
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
|
||||
}
|
||||
@@ -285,4 +302,29 @@ object InteractionListeners {
|
||||
val className = handler.javaClass.name.substringBefore("$")
|
||||
return instantClasses.contains(className)
|
||||
}
|
||||
|
||||
fun addMetadata (ids: IntArray, type: IntType, options: Array<out String>, metadata: InteractionListener.InteractionMetadata) {
|
||||
for (id in ids)
|
||||
for (opt in options)
|
||||
interactions["${type.ordinal}:$id:${opt.lowercase()}"] = metadata
|
||||
}
|
||||
|
||||
fun addMetadata (id: Int, type: IntType, options: Array<out String>, metadata: InteractionListener.InteractionMetadata) {
|
||||
for (opt in options)
|
||||
interactions["${type.ordinal}:$id:${opt.lowercase()}"] = metadata
|
||||
}
|
||||
|
||||
fun addGenericMetadata (options: Array<out String>, type: IntType, metadata: InteractionListener.InteractionMetadata) {
|
||||
for (opt in options)
|
||||
interactions["${type.ordinal}:$opt"] = metadata
|
||||
}
|
||||
|
||||
fun addMetadata (used: Int, with: IntArray, type: IntType, metadata: InteractionListener.UseWithMetadata) {
|
||||
for (id in with)
|
||||
useWithInteractions["${type.ordinal}:$used:$with"] = metadata
|
||||
}
|
||||
|
||||
fun addMetadata (used: Int, with: Int, type: IntType, metadata: InteractionListener.UseWithMetadata) {
|
||||
useWithInteractions["${type.ordinal}:$used:$with"] = metadata
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package core.game.interaction
|
||||
|
||||
import core.game.node.Node
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.world.GameWorld
|
||||
|
||||
typealias UseWithExecutor = (Player, Node, Node, Int) -> Boolean
|
||||
typealias InteractExecutor = (Player, Node, Int) -> Boolean
|
||||
typealias VoidExecutor = (Int) -> Boolean
|
||||
|
||||
enum class QueueStrength {
|
||||
WEAK,
|
||||
NORMAL,
|
||||
STRONG,
|
||||
SOFT
|
||||
}
|
||||
|
||||
open class Script<T> (val execution: T, val persist: Boolean) {
|
||||
var state: Int = 0
|
||||
var nextExecution = 0
|
||||
}
|
||||
|
||||
class Interaction(execution: InteractExecutor, val distance: Int, persist: Boolean) : Script<InteractExecutor>(execution, persist)
|
||||
class UseWithInteraction(execution: UseWithExecutor, val distance: Int, persist: Boolean, val used: Node, val with: Node) : Script<UseWithExecutor>(execution, persist)
|
||||
class QueuedScript(executor: VoidExecutor, val strength: QueueStrength, persist: Boolean) : Script<VoidExecutor>(executor, persist)
|
||||
class QueuedUseWith(executor: UseWithExecutor, val strength: QueueStrength, persist: Boolean, val used: Node, val with: Node) : Script<UseWithExecutor>(executor, persist)
|
||||
@@ -0,0 +1,295 @@
|
||||
package core.game.interaction
|
||||
|
||||
import core.api.*
|
||||
import core.game.node.Node
|
||||
import core.game.node.entity.Entity
|
||||
import core.game.node.entity.npc.NPC
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.item.GroundItem
|
||||
import core.game.node.scenery.Scenery
|
||||
import core.game.world.GameWorld
|
||||
import core.game.world.map.Location
|
||||
import core.game.world.map.path.Pathfinder
|
||||
import core.tools.SystemLogger
|
||||
import java.lang.Integer.max
|
||||
|
||||
class ScriptProcessor(val entity: Entity) {
|
||||
private var apScript: Script<*>? = null
|
||||
private var opScript: Script<*>? = null
|
||||
private var interactTarget: Node? = null
|
||||
private var currentScript: Script<*>? = null
|
||||
private val queue = ArrayList<Script<*>>()
|
||||
|
||||
var delay = 0
|
||||
var interacted = false
|
||||
var apRangeCalled = false
|
||||
var apRange = 10
|
||||
var persistent = false
|
||||
var targetDestination: Location? = null
|
||||
|
||||
fun preMovement() {
|
||||
var allSkipped = false
|
||||
while (!allSkipped) {
|
||||
allSkipped = processQueue()
|
||||
}
|
||||
|
||||
if (isStunned(entity)) return
|
||||
if (entity.delayed()) return
|
||||
|
||||
var canProcess = !entity.delayed()
|
||||
if (entity is Player)
|
||||
canProcess = canProcess && !entity.interfaceManager.isOpened && !entity.interfaceManager.hasChatbox()
|
||||
|
||||
if (entity !is Player) return
|
||||
if (!entity.delayed() && canProcess && interactTarget != null) {
|
||||
if (opScript != null && inOperableDistance()) {
|
||||
face(entity, interactTarget?.centerLocation ?: return)
|
||||
processInteractScript(opScript ?: return)
|
||||
}
|
||||
else if (apScript != null && inApproachDistance(apScript ?: return)) {
|
||||
face(entity, interactTarget?.centerLocation ?: return)
|
||||
processInteractScript(apScript ?: return)
|
||||
}
|
||||
else if (apScript == null && opScript == null && inOperableDistance()) {
|
||||
sendMessage(entity, "Nothing interesting happens.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun postMovement(didMove: Boolean) {
|
||||
if (didMove)
|
||||
entity.clocks[Clocks.MOVEMENT] = GameWorld.ticks + 1
|
||||
var canProcess = !entity.delayed()
|
||||
if (entity is Player)
|
||||
canProcess = canProcess && !entity.interfaceManager.isOpened && !entity.interfaceManager.hasChatbox()
|
||||
|
||||
if (entity !is Player) return
|
||||
if (!entity.delayed() && canProcess && interactTarget != null && !interacted) {
|
||||
if (opScript != null && inOperableDistance()) {
|
||||
face(entity, interactTarget?.centerLocation ?: return)
|
||||
processInteractScript(opScript ?: return)
|
||||
}
|
||||
else if (apScript != null && inApproachDistance(apScript ?: return)) {
|
||||
face(entity, interactTarget?.centerLocation ?: return)
|
||||
processInteractScript(apScript ?: return)
|
||||
}
|
||||
else if (apScript == null && opScript == null && inOperableDistance()) {
|
||||
sendMessage(entity, "Nothing interesting happens.")
|
||||
}
|
||||
}
|
||||
if (canProcess && (apScript != null || opScript != null)) {
|
||||
if (!interacted && !didMove) {
|
||||
sendMessage(entity, "I can't reach that!")
|
||||
reset()
|
||||
}
|
||||
}
|
||||
if (interacted && !apRangeCalled && !persistent) reset()
|
||||
if (interactTarget != null && interactTarget?.isActive != true) reset()
|
||||
}
|
||||
|
||||
fun processQueue() : Boolean {
|
||||
var strongInQueue = false
|
||||
var softInQueue = false
|
||||
var anyExecuted = false
|
||||
for (i in 0 until queue.size) {
|
||||
val script = queue[i]
|
||||
if (script is QueuedScript && script.strength == QueueStrength.STRONG)
|
||||
strongInQueue = true
|
||||
if (script is QueuedUseWith && script.strength == QueueStrength.STRONG)
|
||||
strongInQueue = true
|
||||
if (script is QueuedScript && script.strength == QueueStrength.SOFT)
|
||||
softInQueue = true
|
||||
if (script is QueuedUseWith && script.strength == QueueStrength.SOFT)
|
||||
softInQueue = true
|
||||
}
|
||||
|
||||
if (softInQueue) {
|
||||
removeWeakScripts()
|
||||
removeNormalScripts()
|
||||
if (entity is Player) {
|
||||
entity.interfaceManager.close()
|
||||
entity.interfaceManager.closeChatbox()
|
||||
entity.dialogueInterpreter.close()
|
||||
}
|
||||
}
|
||||
|
||||
if (strongInQueue) {
|
||||
removeWeakScripts()
|
||||
if (entity is Player) {
|
||||
entity.interfaceManager.close()
|
||||
entity.interfaceManager.closeChatbox()
|
||||
entity.dialogueInterpreter.close()
|
||||
}
|
||||
}
|
||||
|
||||
val toRemove = ArrayList<Script<*>>()
|
||||
|
||||
for (i in 0 until queue.size) {
|
||||
when (val script = queue[i]) {
|
||||
is QueuedScript -> {
|
||||
if (entity.delayed() && script.strength != QueueStrength.SOFT)
|
||||
continue
|
||||
if (script.nextExecution > GameWorld.ticks)
|
||||
continue
|
||||
if ((script.strength == QueueStrength.STRONG || script.strength == QueueStrength.SOFT) && entity is Player) {
|
||||
entity.interfaceManager.close()
|
||||
entity.interfaceManager.closeChatbox()
|
||||
entity.dialogueInterpreter.close()
|
||||
}
|
||||
script.nextExecution = GameWorld.ticks + 1
|
||||
val finished = executeScript(script)
|
||||
script.state++
|
||||
if (finished && !script.persist)
|
||||
toRemove.add(script)
|
||||
else if (finished)
|
||||
script.state = 0
|
||||
anyExecuted = true
|
||||
}
|
||||
is QueuedUseWith -> {
|
||||
if (entity.delayed() && script.strength != QueueStrength.SOFT)
|
||||
continue
|
||||
if (entity !is Player) {
|
||||
toRemove.add(script)
|
||||
SystemLogger.logErr(this::class.java, "Tried to queue an item UseWith interaction for a non-player!")
|
||||
continue
|
||||
}
|
||||
if (script.nextExecution > GameWorld.ticks)
|
||||
continue
|
||||
if ((script.strength == QueueStrength.STRONG || script.strength == QueueStrength.SOFT)) {
|
||||
entity.interfaceManager.close()
|
||||
entity.interfaceManager.closeChatbox()
|
||||
entity.dialogueInterpreter.close()
|
||||
}
|
||||
script.nextExecution = GameWorld.ticks + 1
|
||||
val finished = executeScript(script)
|
||||
script.state++
|
||||
if (finished && !script.persist)
|
||||
toRemove.add(script)
|
||||
else if (finished)
|
||||
script.state = 0
|
||||
anyExecuted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queue.removeAll(toRemove.toSet())
|
||||
return !anyExecuted
|
||||
}
|
||||
|
||||
fun isPersist (script: Script<*>) : Boolean {
|
||||
return script.persist
|
||||
}
|
||||
|
||||
fun processInteractScript(script: Script<*>) {
|
||||
if (script.nextExecution < GameWorld.ticks) {
|
||||
val finished = executeScript(script)
|
||||
script.state++
|
||||
if (finished && isPersist(script))
|
||||
script.state = 0
|
||||
interacted = true
|
||||
}
|
||||
}
|
||||
|
||||
fun executeScript(script: Script<*>) : Boolean {
|
||||
currentScript = script
|
||||
when (script) {
|
||||
is Interaction -> return script.execution.invoke(entity as? Player ?: return true, interactTarget ?: return true, script.state)
|
||||
is UseWithInteraction -> return script.execution.invoke(entity as? Player ?: return true, script.used, script.with, script.state)
|
||||
is QueuedScript -> return script.execution.invoke(script.state)
|
||||
is QueuedUseWith -> return script.execution.invoke(entity as? Player ?: return true, script.used, script.with, script.state)
|
||||
}
|
||||
currentScript = null
|
||||
return true
|
||||
}
|
||||
|
||||
fun removeWeakScripts() {
|
||||
queue.removeAll(queue.filter { it is QueuedScript && it.strength == QueueStrength.WEAK || it is QueuedUseWith && it.strength == QueueStrength.WEAK }.toSet())
|
||||
}
|
||||
|
||||
fun removeNormalScripts() {
|
||||
queue.removeAll(queue.filter { it is QueuedScript && it.strength == QueueStrength.NORMAL || it is QueuedUseWith && it.strength == QueueStrength.NORMAL }.toSet())
|
||||
}
|
||||
|
||||
fun inApproachDistance(script: Script<*>) : Boolean {
|
||||
val distance = when (script) {
|
||||
is Interaction -> script.distance
|
||||
is UseWithInteraction -> script.distance
|
||||
else -> 10
|
||||
}
|
||||
targetDestination?.let {
|
||||
return it.location.getDistance(entity.location) <= distance && hasLineOfSight(entity, it)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun inOperableDistance() : Boolean {
|
||||
targetDestination?.let {
|
||||
return it.cardinalTiles.any {loc -> loc == entity.location} && hasLineOfSight(entity, it)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
apScript = null
|
||||
opScript = null
|
||||
apRangeCalled = false
|
||||
interacted = false
|
||||
apRange = 10
|
||||
interactTarget = null
|
||||
persistent = false
|
||||
targetDestination = null
|
||||
resetAnimator(entity as? Player ?: return)
|
||||
}
|
||||
|
||||
fun setInteractionScript(target: Node, script: Script<*>?) {
|
||||
reset()
|
||||
interactTarget = target
|
||||
if (script != null) {
|
||||
apRange = when(script) {
|
||||
is Interaction -> script.distance
|
||||
is UseWithInteraction -> script.distance
|
||||
else -> 10
|
||||
}
|
||||
persistent = script.persist
|
||||
if (apRange == -1)
|
||||
opScript = script
|
||||
else
|
||||
apScript = script
|
||||
targetDestination = when (interactTarget) {
|
||||
is NPC -> DestinationFlag.ENTITY.getDestination(entity, interactTarget)
|
||||
is Scenery -> {
|
||||
val path = Pathfinder.find(entity, interactTarget).points.lastOrNull()
|
||||
if (path == null) {
|
||||
clearScripts(entity)
|
||||
return
|
||||
}
|
||||
Location.create(path.x, path.y, entity.location.z)
|
||||
}
|
||||
is GroundItem -> DestinationFlag.ITEM.getDestination(entity, interactTarget)
|
||||
else -> target.location
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addToQueue(script: Script<*>, strength: QueueStrength) {
|
||||
if (script !is QueuedScript && script !is QueuedUseWith) {
|
||||
SystemLogger.logErr(this::class.java, "Tried to queue ${script::class.java.simpleName} as a queueable script but it's not!")
|
||||
return
|
||||
}
|
||||
if (strength == QueueStrength.STRONG && entity is Player) {
|
||||
entity.interfaceManager.close()
|
||||
entity.interfaceManager.closeChatbox()
|
||||
entity.dialogueInterpreter.close()
|
||||
}
|
||||
script.nextExecution = max(GameWorld.ticks + 1, script.nextExecution)
|
||||
queue.add(script)
|
||||
}
|
||||
|
||||
fun getActiveScript() : Script<*>? {
|
||||
return currentScript ?: getActiveInteraction()
|
||||
}
|
||||
|
||||
private fun getActiveInteraction() : Script<*>? {
|
||||
return opScript ?: apScript
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package core.game.node;
|
||||
|
||||
import core.game.interaction.DestinationFlag;
|
||||
import core.game.interaction.Interaction;
|
||||
import core.game.interaction.InteractPlugin;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.entity.player.Player;
|
||||
import core.game.node.item.Item;
|
||||
@@ -49,7 +49,7 @@ public abstract class Node {
|
||||
/**
|
||||
* The interaction instance.
|
||||
*/
|
||||
protected Interaction interaction;
|
||||
protected InteractPlugin interactPlugin;
|
||||
|
||||
/**
|
||||
* The destination flag.
|
||||
@@ -220,19 +220,19 @@ public abstract class Node {
|
||||
* Gets the interaction.
|
||||
* @return The interaction.
|
||||
*/
|
||||
public Interaction getInteraction() {
|
||||
if (interaction != null && !interaction.isInitialized()) {
|
||||
interaction.setDefault();
|
||||
public InteractPlugin getInteraction() {
|
||||
if (interactPlugin != null && !interactPlugin.isInitialized()) {
|
||||
interactPlugin.setDefault();
|
||||
}
|
||||
return interaction;
|
||||
return interactPlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the interaction.
|
||||
* @param interaction The interaction to set.
|
||||
* @param interactPlugin The interaction to set.
|
||||
*/
|
||||
public void setInteraction(Interaction interaction) {
|
||||
this.interaction = interaction;
|
||||
public void setInteraction(InteractPlugin interactPlugin) {
|
||||
this.interactPlugin = interactPlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package core.game.node.entity;
|
||||
|
||||
import core.game.event.*;
|
||||
import core.game.interaction.DestinationFlag;
|
||||
import core.game.interaction.*;
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.combat.BattleState;
|
||||
import core.game.node.entity.combat.CombatStyle;
|
||||
@@ -9,6 +9,7 @@ import core.game.node.entity.combat.DeathTask;
|
||||
import core.game.node.entity.combat.ImpactHandler;
|
||||
import core.game.node.entity.combat.equipment.ArmourSet;
|
||||
import core.game.node.entity.impl.*;
|
||||
import core.game.node.entity.impl.Properties;
|
||||
import core.game.node.entity.lock.ActionLocks;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.entity.player.Player;
|
||||
@@ -29,10 +30,9 @@ import core.game.world.update.flag.context.Graphics;
|
||||
import core.game.node.entity.combat.CombatSwingHandler;
|
||||
import core.game.world.update.UpdateMasks;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import static core.api.ContentAPIKt.isStunned;
|
||||
|
||||
/**
|
||||
* An entity is a movable node, such as players and NPCs.
|
||||
@@ -109,6 +109,8 @@ public abstract class Entity extends Node {
|
||||
* The reward locks.
|
||||
*/
|
||||
private final ActionLocks locks = new ActionLocks();
|
||||
public final ScriptProcessor scripts = new ScriptProcessor(this);
|
||||
public final int[] clocks = new int[10];
|
||||
|
||||
|
||||
/**
|
||||
@@ -122,6 +124,8 @@ public abstract class Entity extends Node {
|
||||
*/
|
||||
private boolean invisible;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Entity} {@code Object}.
|
||||
* @param name The name of the entity.
|
||||
@@ -209,9 +213,12 @@ public abstract class Entity extends Node {
|
||||
* This methods gets called before the {@link #update()} method.
|
||||
*/
|
||||
public void tick() {
|
||||
scripts.preMovement();
|
||||
dispatch(new TickEvent(GameWorld.getTicks()));
|
||||
skills.pulse();
|
||||
Location old = location != null ? location.transform(0, 0, 0) : Location.create(0,0,0);
|
||||
walkingQueue.update();
|
||||
scripts.postMovement(!Objects.equals(location, old));
|
||||
updateMasks.prepare(this);
|
||||
}
|
||||
|
||||
@@ -953,4 +960,8 @@ public abstract class Entity extends Node {
|
||||
}
|
||||
return occupied;
|
||||
}
|
||||
|
||||
public boolean delayed() {
|
||||
return scripts.getDelay() > GameWorld.getTicks();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package core.game.node.entity.impl;
|
||||
|
||||
import core.game.interaction.Clocks;
|
||||
import core.game.node.entity.Entity;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.world.GameWorld;
|
||||
@@ -121,7 +122,12 @@ public final class Animator {
|
||||
animation.setId(-1);
|
||||
}
|
||||
this.animation = animation;
|
||||
ticks = GameWorld.getTicks() + animation.getDuration();
|
||||
if (animation.getId() != -1) {
|
||||
ticks = GameWorld.getTicks() + animation.getDuration();
|
||||
} else {
|
||||
ticks = 0;
|
||||
}
|
||||
entity.clocks[Clocks.getANIMATION_END()] = ticks;
|
||||
entity.getUpdateMasks().register(entity instanceof NPC ? new NPCAnimation(animation) : new AnimationFlag(animation));
|
||||
priority = animation.getPriority();
|
||||
}
|
||||
@@ -151,6 +157,8 @@ public final class Animator {
|
||||
*/
|
||||
public void reset() {
|
||||
animate(RESET_A);
|
||||
entity.clocks[Clocks.getANIMATION_END()] = 0;
|
||||
ticks = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,7 +166,7 @@ public final class Animator {
|
||||
* @return {@code True} if so.
|
||||
*/
|
||||
public boolean isAnimating() {
|
||||
return animation != null && ticks > GameWorld.getTicks();
|
||||
return animation != null && animation.getId() != -1 && ticks > GameWorld.getTicks();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@ package core.game.node.entity.npc;
|
||||
import core.game.event.NPCKillEvent;
|
||||
import core.cache.def.impl.NPCDefinition;
|
||||
import core.game.dialogue.DialoguePlugin;
|
||||
import core.game.interaction.Interaction;
|
||||
import core.game.interaction.InteractPlugin;
|
||||
import core.game.interaction.MovementPulse;
|
||||
import core.game.node.entity.Entity;
|
||||
import core.game.node.entity.combat.BattleState;
|
||||
@@ -162,7 +162,7 @@ public class NPC extends Entity {
|
||||
this.definition = NPCDefinition.forId(id);
|
||||
super.size = definition.size;
|
||||
super.direction = direction;
|
||||
super.interaction = new Interaction(this);
|
||||
super.interactPlugin = new InteractPlugin(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,14 +213,14 @@ public class NPC extends Entity {
|
||||
if (getViewport().getRegion().isActive()) {
|
||||
Repository.addRenderableNPC(this);
|
||||
}
|
||||
interaction.setDefault();
|
||||
interactPlugin.setDefault();
|
||||
configure();
|
||||
setDefaultBehavior();
|
||||
if (definition.childNPCIds != null) {
|
||||
children = new NPC[definition.childNPCIds.length];
|
||||
for (int i = 0; i < children.length; i++) {
|
||||
NPC npc = children[i] = new NPC(definition.childNPCIds[i]);
|
||||
npc.interaction.setDefault();
|
||||
npc.interactPlugin.setDefault();
|
||||
npc.index = index;
|
||||
npc.size = size;
|
||||
}
|
||||
@@ -671,10 +671,10 @@ public class NPC extends Entity {
|
||||
this.definition = NPCDefinition.forId(id);
|
||||
super.name = definition.getName();
|
||||
super.size = definition.size;
|
||||
super.interaction = new Interaction(this);
|
||||
super.interactPlugin = new InteractPlugin(this);
|
||||
initConfig();
|
||||
configure();
|
||||
interaction.setDefault();
|
||||
interactPlugin.setDefault();
|
||||
if (id == originalId) {
|
||||
getUpdateMasks().unregisterSynced(NPCSwitchId.getOrdinal());
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import core.game.container.impl.BankContainer;
|
||||
import core.game.container.impl.EquipmentContainer;
|
||||
import core.game.container.impl.InventoryListener;
|
||||
import core.game.dialogue.DialogueInterpreter;
|
||||
import core.game.interaction.Interaction;
|
||||
import core.game.interaction.InteractPlugin;
|
||||
import core.game.node.entity.Entity;
|
||||
import core.game.node.entity.combat.BattleState;
|
||||
import core.game.node.entity.combat.CombatStyle;
|
||||
@@ -19,6 +19,7 @@ import content.global.handlers.item.equipment.special.ChinchompaSwingHandler;
|
||||
import core.game.node.entity.npc.NPC;
|
||||
import core.game.node.entity.player.info.*;
|
||||
import core.game.node.entity.player.info.login.LoginConfiguration;
|
||||
import core.game.node.entity.player.info.login.PlayerParser;
|
||||
import core.game.node.entity.player.link.*;
|
||||
import core.game.node.entity.player.link.appearance.Appearance;
|
||||
import core.game.node.entity.player.link.audio.AudioManager;
|
||||
@@ -324,7 +325,7 @@ public class Player extends Entity {
|
||||
public Player(PlayerDetails details) {
|
||||
super(details.getUsername(), ServerConstants.START_LOCATION);
|
||||
super.active = false;
|
||||
super.interaction = new Interaction(this);
|
||||
super.interactPlugin = new InteractPlugin(this);
|
||||
this.details = details;
|
||||
this.direction = Direction.SOUTH;
|
||||
}
|
||||
@@ -523,6 +524,10 @@ public class Player extends Entity {
|
||||
PacketRepository.send(SkillLevel.class, new SkillContext(this, Skills.HITPOINTS));
|
||||
getSkills().setLifepointsUpdate(false);
|
||||
}
|
||||
if (getAttribute("flagged-for-save", false)) {
|
||||
PlayerParser.saveImmediately(this);
|
||||
removeAttribute("flagged-for-save");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -31,6 +31,10 @@ public final class PlayerParser {
|
||||
* @param player The player.
|
||||
*/
|
||||
public static void save(Player player) {
|
||||
player.setAttribute("flagged-for-save", true);
|
||||
}
|
||||
|
||||
public static void saveImmediately(Player player) {
|
||||
new PlayerSaver(player).save();
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ public class GroundItem extends Item {
|
||||
super(item.getId(), item.getAmount(), item.getCharge());
|
||||
super.location = location;
|
||||
super.index = -1;
|
||||
super.interaction.setDefault();
|
||||
super.interactPlugin.setDefault();
|
||||
this.dropper = player;
|
||||
this.dropperUid = player != null ? player.getDetails().getUid() : -1;
|
||||
this.ticks = GameWorld.getTicks();
|
||||
|
||||
@@ -2,7 +2,7 @@ package core.game.node.item;
|
||||
|
||||
import core.cache.def.impl.ItemDefinition;
|
||||
import core.game.interaction.DestinationFlag;
|
||||
import core.game.interaction.Interaction;
|
||||
import core.game.interaction.InteractPlugin;
|
||||
import core.game.interaction.OptionHandler;
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.combat.equipment.DegradableEquipment;
|
||||
@@ -34,7 +34,7 @@ public class Item extends Node{
|
||||
*/
|
||||
public Item() {
|
||||
super("null", null);
|
||||
super.interaction = new Interaction(this);
|
||||
super.interactPlugin = new InteractPlugin(this);
|
||||
this.idHash = -1 << 16 | 1000;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class Item extends Node{
|
||||
super(ItemDefinition.forId(id).getName(), null);
|
||||
super.destinationFlag = DestinationFlag.ITEM;
|
||||
super.index = -1; // Item slot.
|
||||
super.interaction = new Interaction(this);
|
||||
super.interactPlugin = new InteractPlugin(this);
|
||||
this.idHash = id << 16 | charge;
|
||||
this.amount = amount;
|
||||
this.definition = ItemDefinition.forId(id);
|
||||
|
||||
@@ -3,7 +3,7 @@ package core.game.node.scenery;
|
||||
import core.cache.def.impl.VarbitDefinition;
|
||||
import core.cache.def.impl.SceneryDefinition;
|
||||
import core.game.interaction.DestinationFlag;
|
||||
import core.game.interaction.Interaction;
|
||||
import core.game.interaction.InteractPlugin;
|
||||
import core.game.node.Node;
|
||||
import core.game.node.entity.impl.GameAttributes;
|
||||
import core.game.node.entity.player.Player;
|
||||
@@ -145,7 +145,7 @@ public class Scenery extends Node {
|
||||
}
|
||||
super.destinationFlag = DestinationFlag.OBJECT;
|
||||
super.direction = Direction.get(rotation);
|
||||
super.interaction = new Interaction(this);
|
||||
super.interactPlugin = new InteractPlugin(this);
|
||||
this.rotation = rotation;
|
||||
this.id = id;
|
||||
this.location = location;
|
||||
|
||||
@@ -2,6 +2,8 @@ package core.game.system.command.sets
|
||||
|
||||
import content.global.activity.jobs.JobManager
|
||||
import content.global.skill.slayer.Master
|
||||
import core.api.removeAttribute
|
||||
import core.api.getItemName
|
||||
import core.api.sendMessage
|
||||
import core.cache.Cache
|
||||
import core.cache.def.impl.DataMap
|
||||
@@ -197,5 +199,14 @@ class DevelopmentCommandSet : CommandSet(Privilege.ADMIN) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define("itemsearch") {player, args ->
|
||||
val itemName = args.copyOfRange(1, args.size).joinToString(" ").lowercase()
|
||||
for (i in 0 until 15000) {
|
||||
val name = getItemName(i).lowercase()
|
||||
if (name.contains(itemName) || itemName.contains(name))
|
||||
notify(player, "$i: $name")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,6 +289,16 @@ public final class Location extends Node {
|
||||
return locs;
|
||||
}
|
||||
|
||||
public ArrayList<Location> getCardinalTiles() {
|
||||
ArrayList<Location> locs = new ArrayList<>();
|
||||
|
||||
locs.add(transform(0, 1, 0));
|
||||
locs.add(transform(0, -1, 0));
|
||||
locs.add(transform(-1, 0, 0));
|
||||
locs.add(transform(1, 0, 0));
|
||||
return locs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a square of 3 x 3 tiles as an ArrayList<Location>
|
||||
*/
|
||||
|
||||
@@ -191,8 +191,7 @@ class DisconnectionQueue {
|
||||
*/
|
||||
fun save(player: Player, sql: Boolean): Boolean {
|
||||
try {
|
||||
PlayerParser.save(player)
|
||||
return true
|
||||
PlayerParser.saveImmediately(player)
|
||||
} catch (t: Throwable) {
|
||||
t.printStackTrace()
|
||||
}
|
||||
|
||||
@@ -9,12 +9,6 @@ import core.cache.def.impl.NPCDefinition
|
||||
import core.cache.def.impl.SceneryDefinition
|
||||
import core.game.container.Container
|
||||
import core.game.container.impl.BankContainer
|
||||
import core.game.interaction.PluginInteractionManager
|
||||
import core.game.interaction.Interaction
|
||||
import core.game.interaction.MovementPulse
|
||||
import core.game.interaction.NodeUsageEvent
|
||||
import core.game.interaction.Option
|
||||
import core.game.interaction.UseWithHandler
|
||||
import core.game.node.Node
|
||||
import core.game.node.entity.player.Player
|
||||
import core.game.node.entity.player.info.Rights
|
||||
@@ -44,13 +38,11 @@ import core.game.ge.GrandExchange.Companion.getOfferStats
|
||||
import core.game.ge.GrandExchange.Companion.getRecommendedPrice
|
||||
import core.game.ge.GrandExchangeOffer
|
||||
import core.game.ge.PriceIndex
|
||||
import core.game.interaction.IntType
|
||||
import core.game.interaction.InteractionListeners
|
||||
import core.game.interaction.InterfaceListeners
|
||||
import content.global.handlers.iface.ge.StockMarket
|
||||
import content.global.skill.magic.SpellListener
|
||||
import content.global.skill.magic.SpellListeners
|
||||
import content.global.skill.magic.SpellUtils
|
||||
import core.game.interaction.*
|
||||
import core.game.node.entity.player.info.LogType
|
||||
import core.game.node.entity.player.info.PlayerMonitor
|
||||
import core.tools.SystemLogger
|
||||
@@ -78,6 +70,10 @@ object PacketProcessor {
|
||||
var pkt: Packet
|
||||
while (countThisCycle-- > 0) {
|
||||
pkt = queue.tryPop(Packet.NoProcess())
|
||||
if (pkt is Packet.NoProcess) {
|
||||
queue.clear()
|
||||
return
|
||||
}
|
||||
try {
|
||||
process(pkt)
|
||||
} catch (e: Exception) {
|
||||
@@ -457,6 +453,7 @@ object PacketProcessor {
|
||||
|
||||
player.face(null)
|
||||
player.faceLocation(null)
|
||||
player.scripts.reset()
|
||||
|
||||
player.pulseManager.run(object : MovementPulse(player, Location.create(x,y,player.location.z), isRunning) {
|
||||
override fun pulse(): Boolean {
|
||||
@@ -569,6 +566,7 @@ object PacketProcessor {
|
||||
if (node.id != nodeId)
|
||||
return sendClearMinimap(player)
|
||||
|
||||
player.scripts.reset()
|
||||
if (player.zoneMonitor.useWith(item, node))
|
||||
return
|
||||
if (InteractionListeners.run(item, node, type, player))
|
||||
@@ -590,13 +588,14 @@ object PacketProcessor {
|
||||
private fun processGroundItemAction(pkt: Packet.GroundItemAction) {
|
||||
val item = GroundItemManager.get(pkt.id, Location.create(pkt.x, pkt.y, pkt.player.location.z), pkt.player)
|
||||
val player = pkt.player
|
||||
player.scripts.reset()
|
||||
|
||||
if (item == null) {
|
||||
return sendClearMinimap(player)
|
||||
}
|
||||
val option = item.interaction[pkt.optIndex]
|
||||
if (option == null) {
|
||||
Interaction.handleInvalidInteraction(player, item, Option.NULL)
|
||||
InteractPlugin.handleInvalidInteraction(player, item, Option.NULL)
|
||||
return sendClearMinimap(player)
|
||||
}
|
||||
if (PluginInteractionManager.handle(player, item, option))
|
||||
@@ -613,6 +612,7 @@ object PacketProcessor {
|
||||
if (pkt.otherIndex !in 1 until ServerConstants.MAX_PLAYERS) {
|
||||
return sendClearMinimap(player)
|
||||
}
|
||||
player.scripts.reset()
|
||||
val other = Repository.players[pkt.otherIndex]
|
||||
if (other == null || !other.isActive)
|
||||
return sendClearMinimap(player)
|
||||
@@ -642,7 +642,7 @@ object PacketProcessor {
|
||||
|
||||
if (scenery == null || scenery.id != objId || !scenery.isActive) {
|
||||
player.debug("[SCENERY INTERACT] NULL OR MISMATCH OR INACTIVE")
|
||||
Interaction.handleInvalidInteraction(player, scenery, Option.NULL)
|
||||
InteractPlugin.handleInvalidInteraction(player, scenery, Option.NULL)
|
||||
return sendClearMinimap(player)
|
||||
}
|
||||
|
||||
@@ -651,7 +651,7 @@ object PacketProcessor {
|
||||
|
||||
if (option == null) {
|
||||
player.debug("[SCENERY INTERACT] NULL OPTION")
|
||||
Interaction.handleInvalidInteraction(player, scenery, Option.NULL)
|
||||
InteractPlugin.handleInvalidInteraction(player, scenery, Option.NULL)
|
||||
return sendClearMinimap(player)
|
||||
}
|
||||
|
||||
@@ -665,6 +665,7 @@ object PacketProcessor {
|
||||
}
|
||||
player.debug("------------------------------------------------")
|
||||
|
||||
player.scripts.reset()
|
||||
if (InteractionListeners.run(wrapperChild.id, IntType.SCENERY, option.name, player, wrapperChild))
|
||||
return
|
||||
if (PluginInteractionManager.handle(player, wrapperChild))
|
||||
@@ -681,7 +682,7 @@ object PacketProcessor {
|
||||
val option = wrapperChild.interaction[pkt.optIndex]
|
||||
|
||||
if (option == null) {
|
||||
Interaction.handleInvalidInteraction(pkt.player, npc, Option.NULL)
|
||||
InteractPlugin.handleInvalidInteraction(pkt.player, npc, Option.NULL)
|
||||
return sendClearMinimap(pkt.player)
|
||||
}
|
||||
|
||||
@@ -696,6 +697,7 @@ object PacketProcessor {
|
||||
}
|
||||
pkt.player.debug("---------------------------------")
|
||||
|
||||
pkt.player.scripts.reset()
|
||||
if (InteractionListeners.run(wrapperChild.id, IntType.NPC,option.name,pkt.player,npc))
|
||||
return
|
||||
if (PluginInteractionManager.handle(pkt.player, wrapperChild, option))
|
||||
@@ -714,6 +716,7 @@ object PacketProcessor {
|
||||
if (pkt.player.locks.isInteractionLocked)
|
||||
return
|
||||
item.interaction.handleItemOption(pkt.player, option, container)
|
||||
pkt.player.scripts.reset()
|
||||
pkt.player.debug("[ITEM INTERACT] ID: ${item.id}, Slot: ${pkt.slot}, Opt: ${option.name}")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user