From 97561caeca7e9518ff7e4961a57257c5298bdf25 Mon Sep 17 00:00:00 2001 From: gumi Date: Fri, 24 Jul 2020 14:06:43 -0400 Subject: add a reusable event dispatching system --- npc/functions/event-listeners.txt | 169 ++++++++++++++++++++++++++++++++++++++ npc/functions/player-cache.txt | 28 +------ npc/scripts.conf | 1 + 3 files changed, 174 insertions(+), 24 deletions(-) create mode 100644 npc/functions/event-listeners.txt diff --git a/npc/functions/event-listeners.txt b/npc/functions/event-listeners.txt new file mode 100644 index 00000000..111b2737 --- /dev/null +++ b/npc/functions/event-listeners.txt @@ -0,0 +1,169 @@ +// Event listeners work similar to the JavaScript event dispatching system, +// except that the event object is a hash table. +// +// Javascript reference: +// https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events +// +// Example usage: +/* +// Create a handler function: +public function myHandler { + // ... do something with getarg(0) +} + +// Create a new event: +$@event = htnew(); + +// Listen for the event: +addEventListener($@event, "myHandler"); + +// Dispatch the event: +dispatchEvent($@event, "misc data"); +*/ + + +/** + * getEventHt() => ht id + * + * Parses a hash table "event" expression and creates one when necessary. + * The following are all valid: + * + * getEventHt(.@ht); // variable + * getEventHt(72); // arbitrary integer (global namespace) + * getEventHt("some event"); // arbitrary string (global namespace) + * + * NOTE: This is only called internally; you don't have to use this function + * + * @param 0 - a variable / string / integer + * @return the hash table id + */ +function script getEventHt { + .@ht = ((getdatatype(getarg(0)) & (DATATYPE_VAR | DATATYPE_INT)) != 0) ? getarg(0) : 0; + + if (htexists(.@ht)) { + // this is already a valid hash table + return getarg(0); + } else if ((getdatatype(getarg(0)) & DATATYPE_VAR) != 0) { + // the hash table doesn't exist yet: create one and store it in the + // provided variable + + if ((getdatatype(getarg(0)) & DATATYPE_STR) != 0) { + consolemes(CONSOLEMES_ERROR, "getEventHt: variable must be an integer variable (cannot use %s)", data_to_string(getarg(0))); + end; + } + + if (getarg(0) > 0) { + consolemes(CONSOLEMES_WARNING, "getEventHt: variable is already assigned (overwriting %s)", data_to_string(getarg(0))); + } + + return set(getarg(0), htnew()); + } else { + // global event listener namespace + if ($@GLOBAL_EVTL_HT < 1) { + $@GLOBAL_EVTL_HT = htnew(); + + // the global scope is new: create a new one + .@ht = htnew(); + htput($@GLOBAL_EVTL_HT, data_to_string(getarg(0)), "" + .@ht); + return .@ht; + } else if ((.@ht$ = htget($@GLOBAL_EVTL_HT, data_to_string(getarg(0)), "")) != "") { + // found in the global scope + return atoi(.@ht$); + } else { + // not found in the global scope: create new + .@ht = htnew(); + htput($@GLOBAL_EVTL_HT, data_to_string(getarg(0)), "" + .@ht); + return .@ht; + } + } +} + +/** + * addEventListener(, "") => void + * + * Register a public local function in the current NPC as an event handler for + * the given hash table. Can also register for another NPC using the syntax + * `npcName::function` + * + * NOTE: only one handler may be assigned per NPC per Event + * + * @param 0 - the event to listen to (string, variable, or hash table) + * @param 1 - the event handler (must be a public local function) + */ +function script addEventListener { + .@ht = getEventHt(getarg(0)); + .@pos = strpos(getarg(1), "::"); + + if (.@pos > -1) { + .@id = getnpcid(substr(getarg(1), 0, .@pos - 1)); + .@function$ = substr(getarg(1), .@pos + 2, getstrlen(getarg(1)) - 1); + } else { + .@id = getnpcid(); + .@function$ = getarg(1); + } + + if (.@id == 0) { + consolemes(CONSOLEMES_WARNING, "addEventListener: the target NPC doesn't exist"); + return; + } + + // TODO: check that the target function exists + return htput(.@ht, "" + .@id, .@function$); +} + +/** + * dispatchEvent({, ...}) => bool + * + * Calls every event handler for the given hash table and passes the given + * arguments (if any) + * + * @param 0 - the event to dispatch (string, variable, or hash table) + * @return true when handlers were called, else false + */ +function script dispatchEvent { + .@ht = getEventHt(getarg(0)); + + if (htsize(.@ht) < 1) { + // no handler to call + return false; + } + + .@it = htiterator(.@ht); + + freeloop(true); + for (.@npc$ = htinextkey(.@it); hticheck(.@it); .@npc$ = htinextkey(.@it)) { + if (.@npc$ == "") { + continue; + } + + .@func$ = htget(.@ht, .@npc$, ""); + + // this is ugly but we don't have rest parameters (...args) to apply args to function calls + switch (getargcount()) { + case 1: + callfunctionofnpc(atoi(.@npc$), .@func$); + break; + case 2: + callfunctionofnpc(atoi(.@npc$), .@func$, getarg(1)); + break; + case 3: + callfunctionofnpc(atoi(.@npc$), .@func$, getarg(1), getarg(2)); + break; + case 4: + callfunctionofnpc(atoi(.@npc$), .@func$, getarg(1), getarg(2), getarg(3)); + break; + case 5: + callfunctionofnpc(atoi(.@npc$), .@func$, getarg(1), getarg(2), getarg(3), getarg(4)); + break; + default: + consolemes(CONSOLEMES_WARNING, "dispatchEvent: truncating to 5 arguments (%d given)", getargcount() - 1); + case 6: + callfunctionofnpc(atoi(.@npc$), .@func$, getarg(1), getarg(2), getarg(3), getarg(4), getarg(5)); + break; + } + } + freeloop(false); + + htidelete(.@it); + return true; +} diff --git a/npc/functions/player-cache.txt b/npc/functions/player-cache.txt index 7c281af9..204d1fe1 100644 --- a/npc/functions/player-cache.txt +++ b/npc/functions/player-cache.txt @@ -216,8 +216,7 @@ * @param 0 - the public local function */ public function addNameHandler { - htput(.name_handlers, "" + getnpcid(), getarg(0)); - return; + return addEventListener(.name_handlers, getarg(0)); } /** @@ -229,8 +228,7 @@ * @param 0 - the public local function */ public function addVaultHandler { - htput(.vault_handlers, "" + getnpcid(), getarg(0)); - return; + return addEventListener(.vault_handlers, getarg(0)); } @@ -253,16 +251,7 @@ if (.@old$ != "" && .@old$ != .account_to_name$[playerattached()]) { // fire event handlers - .@it = htiterator(.name_handlers); - for (.@npc$ = htinextkey(.@it); hticheck(.@it); .@npc$ = htinextkey(.@it)) { - if (.@npc$ == "") { - continue; - } - - .@func$ = htget(.name_handlers, .@npc$, ""); - callfunctionofnpc(atoi(.@npc$), .@func$, .@old$); - } - htidelete(.@it); + dispatchEvent(.name_handlers, .@old$); return true; } @@ -285,16 +274,7 @@ if (.@old > 0 && .@old != .vault_to_account[getvaultid()]) { // fire event handlers - .@it = htiterator(.vault_handlers); - for (.@npc$ = htinextkey(.@it); hticheck(.@it); .@npc$ = htinextkey(.@it)) { - if (.@npc$ == "") { - continue; - } - - .@func$ = htget(.vault_handlers, .@npc$, ""); - callfunctionofnpc(atoi(.@npc$), .@func$, .@old, .@old$); - } - htidelete(.@it); + dispatchEvent(.vault_handlers, .@old, .@old$); return true; } diff --git a/npc/scripts.conf b/npc/scripts.conf index 9714b492..d86046ad 100644 --- a/npc/scripts.conf +++ b/npc/scripts.conf @@ -14,6 +14,7 @@ "npc/functions/warp.txt", "npc/functions/gender.txt", "npc/functions/bitwise.txt", +"npc/functions/event-listeners.txt", // Misc functions "npc/functions/main.txt", -- cgit v1.2.3-60-g2f50