summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHaru <haru@dotalux.com>2020-05-27 12:40:47 +0200
committerGitHub <noreply@github.com>2020-05-27 12:40:47 +0200
commitfcffb676132949852adfef2fd3f814485f964709 (patch)
treeb00e7b948150ab9c500e9d61b9a11cdd0fe26ad0
parent3d9317d3a0bb200b9262be8fb882f33b40bfbd2e (diff)
parent6aed6b4f7c669c3760c5ebd32b40fbb7da1d5715 (diff)
downloadhercules-fcffb676132949852adfef2fd3f814485f964709.tar.gz
hercules-fcffb676132949852adfef2fd3f814485f964709.tar.bz2
hercules-fcffb676132949852adfef2fd3f814485f964709.tar.xz
hercules-fcffb676132949852adfef2fd3f814485f964709.zip
Merge pull request #2142 from Helianthella/export2
allow local NPC functions to be public or private
-rw-r--r--conf/map/script.conf10
-rw-r--r--doc/script_commands.txt89
-rw-r--r--npc/dev/test.txt40
-rw-r--r--src/map/map.h8
-rw-r--r--src/map/npc.c13
-rw-r--r--src/map/npc.h6
-rw-r--r--src/map/script.c553
-rw-r--r--src/map/script.h21
-rw-r--r--src/plugins/HPMHooking/HPMHooking.Defs.inc6
-rw-r--r--src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc4
-rw-r--r--src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc1
-rw-r--r--src/plugins/HPMHooking/HPMHooking_map.Hooks.inc39
12 files changed, 591 insertions, 199 deletions
diff --git a/conf/map/script.conf b/conf/map/script.conf
index fc4f26965..4eb84edf4 100644
--- a/conf/map/script.conf
+++ b/conf/map/script.conf
@@ -59,6 +59,16 @@ script_configuration: {
// Defaults to INT_MAX.
//input_max_value: 2147483647
input_max_value: 10000000
+
+ // Specifies whether functions not explicitly marked with a "private" or
+ // "public" keyword should be treated as "private" by default.
+ // Default: true
+ functions_private_by_default: true
+
+ // Specifies whether public functions can be invoked as NPC events. This
+ // allows, for example, to use a `public function OnDeath { ... }` instead
+ // of a `OnDeath:` label for mob death events.
+ functions_as_events: false
}
import: "conf/import/script.conf"
diff --git a/doc/script_commands.txt b/doc/script_commands.txt
index 3c0b37a85..a597dfaa2 100644
--- a/doc/script_commands.txt
+++ b/doc/script_commands.txt
@@ -1945,58 +1945,64 @@ will result in error and termination of the script.
---------------------------------------
-*function <function name>;
-*<function name>{(<argument>, ...<argument>)};
-*function <function name> {
+{public | private} *function <function name>;
+{public | private} *function <function name> {
<code>
}
-This works like callfunc(), and is used for cleaner and faster scripting.
-The function must be defined and used within a script, and works like a
-label with arguments.
-Note that the name may only contain alphanumeric characters and underscore.
+In its first form, this syntax declares a local function so it can later be
+defined. In its second form, the syntax both declares and defines a local
+function. Local functions must be defined before being used. Note that the name
+may only contain alphanumeric characters and underscore. Once defined, they can
+be called from the current script as if they were regular built-in commands, and
+can also be called from other scripts if they are marked as public. Local
+functions may be marked as public by simply adding "public" prior to the
+function definition. Functions not marked as public are private by default and
+cannot be called from another script.
Usage:
1. Declare the function.
function <function name>;
2. Call the function anywhere within the script.
- It can also return a value when used with parentheses.
- <function name>;
- 3. Define the function within the script.
+ <function name>();
+ 3. Define the function by adding its script.
<function name> {<code>}
+ Step 1 is optional if the function is defined prior to being called.
+
Example:
-prontera,154,189,4 script Item Seller 767,{
/* Function declaration */
- function SF_Selling;
+ function MyFunction;
- if (Zeny > 50) {
- mes("Welcome!");
- /* Function call */
- SF_Selling();
- } else {
- mes("You need 50z, sorry!");
- }
- close();
+ /* Function call */
+ MyFunction();
/* Function definition */
- function SF_Selling {
- mes("Would you like to buy a phracon for 50z?");
- next();
- if (select("Yes", "No, thanks") == 1) {
- Zeny -= 50;
- getitem(Phracon, 1);
- mes("Thank you!");
- }
+ function MyFunction {
+ // (do something)
return;
}
-}
+
+
+Example with public functions:
+
+ /* Function declaration + definition */
+ public function myFunction {
+ /* notice the "public" before the "function" keyword */
+ return;
+ }
+
+ /* Local call */
+ myFunction();
+
+ /* Call from another script */
+ "npc name"::myFunction();
+
Example with parameters and return value:
-prontera,150,150,0 script TestNPC 123,{
/* Function declaration */
function MyAdd;
@@ -2005,18 +2011,35 @@ prontera,150,150,0 script TestNPC 123,{
input(.@a);
input(.@b);
/* Function call */
- mes(.@a+" + "+.@b+" = "+MyAdd(.@a, .@b));
+ mesf("%i + %i = %i", .@a, .@b, MyAdd(.@a, .@b));
close();
/* Function definition */
function MyAdd {
- return(getarg(0)+getarg(1));
+ return (getarg(0) + getarg(1));
}
-}
---------------------------------------
+*<function name>({<arg>...})
+*"<npc name>"::<function name>({<arg>...})
+*callfunctionofnpc("<function name>", "<npc name>"{, <arg>...});
+
+In its first form, calls a previously defined local function. In its second
+form, calls a previously defined public local function of another NPC. If the
+name of the target NPC or the name of the local function is not known
+beforehand, callfunctionofnpc() can be used instead of the second form.
+See function() above for more details.
+
+Example:
+
+ MyFunction(arg1, arg2, arg3);
+ "MyNPC"::MyFunction(arg1, arg2, arg3);
+ callfunctionofnpc("MyNPC", "MyFunction", arg1, arg2, arg3);
+
+---------------------------------------
+
*is_function("<function name>")
This command checks whether or not a function exists and returns its type.
diff --git a/npc/dev/test.txt b/npc/dev/test.txt
index a9e78489a..c8c842055 100644
--- a/npc/dev/test.txt
+++ b/npc/dev/test.txt
@@ -116,6 +116,40 @@ function script F_TestVarOfAnotherNPC {
end;
}
+- script export test FAKE_NPC,{
+
+ function OnInit {
+ // functions labels should not be able to be called as events
+ // if a regression occurs, this function could end up being called when
+ // Hercules processes OnInit event calls (issue #2137)
+
+ // NOTE: If script_config.functions_as_events is enabled (defaults: off)
+ // and this this function is marked as public, it will trigger the
+ // warning and fail the unit test regardless.
+
+ $@something_bad_happened[0] = true;
+ end;
+ }
+
+ private function Private {
+ // function explicitly marked as private
+ return;
+ }
+
+ public function Public {
+ // this is for testing public local functions and ownership of the
+ // script
+
+ return getnpcid();
+ }
+
+ public function RefTest {
+ // this is to check if references are passed around properly
+
+ return set(getarg(0), 1337);
+ }
+}
+
function script HerculesSelfTestHelper {
if (.once > 0)
return .errors;
@@ -785,6 +819,12 @@ function script HerculesSelfTestHelper {
callsub(OnCheck, "data_to_string (string variable)", data_to_string(.@x$), ".@x$");
callsub(OnCheck, "data_to_string (integer variable)", data_to_string(.@x), ".@x");
+ "export test"::RefTest(.@refTest = 69);
+ callsub(OnCheck, "function as event (regression)", $@something_bad_happened[0], false);
+ callsub(OnCheck, "public local function ownership", "export test"::Public(), getnpcid());
+ callsub(OnCheck, "public local function var reference test", .@refTest, 1337);
+ callsub(OnCheck, "programatic public local call", callfunctionofnpc("export test", "RefTest", .@refTest = 1), 1337);
+
if (.errors) {
consolemes(CONSOLEMES_DEBUG, "Script engine self-test [ \033[0;31mFAILED\033[0m ]");
consolemes(CONSOLEMES_DEBUG, "**** The test was completed with " + .errors + " errors. ****");
diff --git a/src/map/map.h b/src/map/map.h
index 64736a6f5..17f210bc3 100644
--- a/src/map/map.h
+++ b/src/map/map.h
@@ -329,6 +329,14 @@ enum bl_type {
enum npc_subtype { WARP, SHOP, SCRIPT, CASHSHOP, TOMB };
+/** optional flags for script labels, used by the label db */
+enum script_label_flags {
+ /** the label can be called from outside the local scope of the NPC */
+ LABEL_IS_EXTERN = 0x1,
+ /** the label is a public or private local NPC function */
+ LABEL_IS_USERFUNC = 0x2,
+};
+
/**
* Race type IDs.
*
diff --git a/src/map/npc.c b/src/map/npc.c
index 40ec380ee..2f03623e4 100644
--- a/src/map/npc.c
+++ b/src/map/npc.c
@@ -389,7 +389,10 @@ static int npc_event_export(struct npc_data *nd, int i)
Assert_ret(i >= 0 && i < nd->u.scr.label_list_num);
lname = nd->u.scr.label_list[i].name;
pos = nd->u.scr.label_list[i].pos;
- if ((lname[0] == 'O' || lname[0] == 'o') && (lname[1] == 'N' || lname[1] == 'n')) {
+
+ if ((nd->u.scr.label_list[i].flags & LABEL_IS_EXTERN) != 0
+ && ((nd->u.scr.label_list[i].flags & LABEL_IS_USERFUNC) == 0
+ || script->config.functions_as_events)) {
struct event_data *ev;
struct linkdb_node **label_linkdb = NULL;
char buf[EVENT_NAME_LENGTH];
@@ -3054,11 +3057,11 @@ static int npc_unload(struct npc_data *nd, bool single, bool unload_mobs)
aFree(nd->u.shop.shop_item); /// src check for duplicate shops. [Orcao]
} else if (nd->subtype == SCRIPT) {
char evname[EVENT_NAME_LENGTH];
-
+
snprintf(evname, ARRAYLENGTH(evname), "%s::OnNPCUnload", nd->exname);
struct event_data *ev = strdb_get(npc->ev_db, evname);
-
+
if (ev != NULL)
script->run_npc(nd->u.scr.script, ev->pos, 0, nd->bl.id); /// Run OnNPCUnload.
@@ -3665,6 +3668,7 @@ static void npc_convertlabel_db(struct npc_label_list *label_list, const char *f
for( i = 0; i < script->label_count; i++ ) {
const char* lname = script->get_str(script->labels[i].key);
int lpos = script->labels[i].pos;
+ enum script_label_flags flags = script->labels[i].flags;
struct npc_label_list* label;
const char *p;
size_t len;
@@ -3686,6 +3690,7 @@ static void npc_convertlabel_db(struct npc_label_list *label_list, const char *f
safestrncpy(label->name, lname, sizeof(label->name));
label->pos = lpos;
+ label->flags = flags;
}
}
@@ -5606,7 +5611,7 @@ static int npc_reload(void)
npc->npc_last_npd = NULL;
npc->npc_last_path = NULL;
npc->npc_last_ref = NULL;
-
+
const int npc_new_min = npc->npc_id;
struct s_mapiterator *iter = mapit_geteachiddb();
diff --git a/src/map/npc.h b/src/map/npc.h
index 1585a2bc8..16d7a984b 100644
--- a/src/map/npc.h
+++ b/src/map/npc.h
@@ -56,9 +56,15 @@ enum npc_shop_types {
struct npc_timerevent_list {
int timer,pos;
};
+
+/** list of labels within a NPC (used internally by the label db) */
struct npc_label_list {
+ /** label name */
char name[NAME_LENGTH];
+ /** start point within the script */
int pos;
+ /** optional label flags */
+ enum script_label_flags flags;
};
struct npc_barter_currency {
diff --git a/src/map/script.c b/src/map/script.c
index 347ef3d2a..0fd8bda2a 100644
--- a/src/map/script.c
+++ b/src/map/script.c
@@ -848,79 +848,134 @@ static const char *parse_callfunc(const char *p, int require_paren, int is_custo
nullpo_retr(NULL, p);
// is need add check for arg null pointer below?
- func = script->add_word(p);
- if (script->str_data[func].type == C_FUNC) {
- script->syntax.nested_call++;
- if (script->syntax.last_func != -1) {
- if (script->str_data[func].val == script->buildin_lang_macro_offset) {
- script->syntax.lang_macro_active = true;
- macro = true;
- } else if (script->str_data[func].val == script->buildin_lang_macro_fmtstring_offset) {
- script->syntax.lang_macro_fmtstring_active = true;
- macro = true;
- }
+
+ if (*p == '"') {
+ p2 = ++p; // jump to the start of the word
+
+ // find the closing quote
+ while (*p2 != '"') {
+ ++p2;
}
- if( !macro ) {
- // buildin function
+ if (p2[1] == ':' && p2[2] == ':') {
+ func = script->add_str("callfunctionofnpc");
+ arg = "*"; // we already take care of the "vs" part of "vs*"
+
+ script->syntax.nested_call++;
script->syntax.last_func = script->str_data[func].val;
script->addl(func);
script->addc(C_ARG);
- }
- arg = script->buildin[script->str_data[func].val];
- if (script->str_data[func].deprecated)
- DeprecationWarning(p);
- if( !arg ) arg = &null_arg; // Use a dummy, null string
- } else if( script->str_data[func].type == C_USERFUNC || script->str_data[func].type == C_USERFUNC_POS ) {
- // script defined function
- script->addl(script->buildin_callsub_ref);
- script->addc(C_ARG);
- script->addl(func);
- arg = script->buildin[script->str_data[script->buildin_callsub_ref].val];
- if( *arg == 0 )
- disp_error_message("parse_callfunc: callsub has no arguments, please review its definition",p);
- if( *arg != '*' )
- ++arg; // count func as argument
+ script->addc(C_STR);
+ do {
+ script->addb(*p++); // npc name
+ } while (p < p2);
+ script->addb(0);
+
+ p = p2 + 3; // skip to start of func name
+ p2 = script->skip_word(p);
+
+ script->addc(C_STR);
+ do {
+ script->addb(*p++); // func name
+ } while (p < p2);
+ script->addb(0);
+
+ p = p2; // skip to just before the ()
+ } else {
+ disp_error_message("script:parse_callfunc: invalid public function call syntax!", p2 + 1);
+ }
} else {
+ func = script->add_word(p);
+ if (script->str_data[func].type == C_FUNC) {
+ script->syntax.nested_call++;
+
+ if (script->syntax.last_func != -1) {
+ if (script->str_data[func].val == script->buildin_lang_macro_offset) {
+ script->syntax.lang_macro_active = true;
+ macro = true;
+ } else if (script->str_data[func].val == script->buildin_lang_macro_fmtstring_offset) {
+ script->syntax.lang_macro_fmtstring_active = true;
+ macro = true;
+ }
+ }
+
+ if (!macro) {
+ // buildin function
+ script->syntax.last_func = script->str_data[func].val;
+ script->addl(func);
+ script->addc(C_ARG);
+ }
+
+ arg = script->buildin[script->str_data[func].val];
+
+ if (script->str_data[func].deprecated == 1) {
+ DeprecationWarning(p);
+ }
+
+ if (arg == NULL) {
+ arg = &null_arg; // Use a dummy, null string
+ }
+ } else if (script->str_data[func].type == C_USERFUNC || script->str_data[func].type == C_USERFUNC_POS) {
+ // script defined function
+ script->addl(script->buildin_callsub_ref);
+ script->addc(C_ARG);
+ script->addl(func);
+ arg = script->buildin[script->str_data[script->buildin_callsub_ref].val];
+
+ if (*arg == 0) {
+ disp_error_message("script:parse_callfunc: callsub has no arguments, please review its definition", p);
+ }
+
+ if (*arg != '*') {
+ ++arg; // count func as argument
+ }
+ } else {
#ifdef SCRIPT_CALLFUNC_CHECK
- const char* name = script->get_str(func);
- if( !is_custom && strdb_get(script->userfunc_db, name) == NULL ) {
+ const char *name = script->get_str(func);
+ if (is_custom == 0 && strdb_get(script->userfunc_db, name) == NULL) {
#endif
- disp_error_message("parse_line: expect command, missing function name or calling undeclared function",p);
+ disp_error_message("script:parse_callfunc: expect command, missing function name or calling undeclared function", p);
#ifdef SCRIPT_CALLFUNC_CHECK
- } else {;
- script->addl(script->buildin_callfunc_ref);
- script->addc(C_ARG);
- script->addc(C_STR);
- while( *name ) script->addb(*name ++);
- script->addb(0);
- arg = script->buildin[script->str_data[script->buildin_callfunc_ref].val];
- if( *arg != '*' ) ++ arg;
- }
+ } else {
+ script->addl(script->buildin_callfunc_ref);
+ script->addc(C_ARG);
+ script->addc(C_STR);
+
+ while (*name != '\0') {
+ script->addb(*name++);
+ }
+
+ script->addb(0);
+ arg = script->buildin[script->str_data[script->buildin_callfunc_ref].val];
+
+ if (*arg != '*') {
+ ++ arg;
+ }
+ }
#endif
+ }
}
p = script->skip_word(p);
p = script->skip_space(p);
script->syntax.curly[script->syntax.curly_count].type = TYPE_ARGLIST;
script->syntax.curly[script->syntax.curly_count].count = 0;
- if( *p == ';' )
- {// <func name> ';'
+
+ if (*p == ';') {
+ // <func name> ';'
script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_NO_PAREN;
- } else if( *p == '(' && *(p2=script->skip_space(p+1)) == ')' )
- {// <func name> '(' ')'
+ } else if (*p == '(' && *(p2 = script->skip_space(p + 1)) == ')') {
+ // <func name> '(' ')'
script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_PAREN;
p = p2;
- /*
- } else if( 0 && require_paren && *p != '(' )
- {// <func name>
- script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_NO_PAREN;
- */
- } else {// <func name> <arg list>
- if( require_paren ) {
- if( *p != '(' )
- disp_error_message("need '('",p);
+ } else {
+ // <func name> <arg list>
+ if (require_paren == 1) {
+ if (*p != '(') {
+ disp_error_message("script:parse_callfunc: need '('", p);
+ }
+
++p; // skip '('
script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_PAREN;
} else if( *p == '(' ) {
@@ -928,41 +983,65 @@ static const char *parse_callfunc(const char *p, int require_paren, int is_custo
} else {
script->syntax.curly[script->syntax.curly_count].flag = ARGLIST_NO_PAREN;
}
+
++script->syntax.curly_count;
- while( *arg ) {
- p2=script->parse_subexpr(p,-1);
- if( p == p2 )
- break; // not an argument
- if( *arg != '*' )
- ++arg; // next argument
- p=script->skip_space(p2);
- if( *arg == 0 || *p != ',' )
- break; // no more arguments
+ while (*arg != '\0') {
+ p2 = script->parse_subexpr(p, -1);
+
+ if (p == p2) {
+ // not an argument
+ break;
+ }
+
+ if (*arg != '*') {
+ // next argument
+ ++arg;
+ }
+
+ p = script->skip_space(p2);
+
+ if (*arg == 0 || *p != ',') {
+ // no more arguments
+ break;
+ }
+
++p; // skip comma
}
+
--script->syntax.curly_count;
}
- if( arg && *arg && *arg != '?' && *arg != '*' )
- disp_error_message2("parse_callfunc: not enough arguments, expected ','", p, script->config.warn_func_mismatch_paramnum);
- if( script->syntax.curly[script->syntax.curly_count].type != TYPE_ARGLIST )
- disp_error_message("parse_callfunc: DEBUG last curly is not an argument list",p);
- if( script->syntax.curly[script->syntax.curly_count].flag == ARGLIST_PAREN ) {
- if( *p != ')' )
- disp_error_message("parse_callfunc: expected ')' to close argument list",p);
+
+ if (arg != NULL && *arg != '\0' && *arg != '?' && *arg != '*') {
+ disp_error_message2("script:parse_callfunc: not enough arguments, expected ','", p, script->config.warn_func_mismatch_paramnum);
+ }
+
+ if (script->syntax.curly[script->syntax.curly_count].type != TYPE_ARGLIST) {
+ disp_error_message("parse_callfunc: DEBUG last curly is not an argument list", p);
+ }
+
+ if (script->syntax.curly[script->syntax.curly_count].flag == ARGLIST_PAREN) {
+ if (*p != ')') {
+ disp_error_message("script:parse_callfunc: expected ')' to close argument list", p);
+ }
+
++p;
- if (script->str_data[func].val == script->buildin_lang_macro_offset)
+ if (script->str_data[func].val == script->buildin_lang_macro_offset) {
script->syntax.lang_macro_active = false;
- else if (script->str_data[func].val == script->buildin_lang_macro_fmtstring_offset)
+ } else if (script->str_data[func].val == script->buildin_lang_macro_fmtstring_offset) {
script->syntax.lang_macro_fmtstring_active = false;
+ }
}
if (!macro) {
- if (0 == --script->syntax.nested_call)
+ if (0 == --script->syntax.nested_call) {
script->syntax.last_func = -1;
+ }
+
script->addc(C_FUNC);
}
+
return p;
}
@@ -1230,16 +1309,29 @@ static int script_string_dup(char *str)
*------------------------------------------*/
static const char *parse_simpleexpr(const char *p)
{
- p=script->skip_space(p);
+ p = script->skip_space(p);
nullpo_retr(NULL, p);
- if (*p == ';' || *p == ',')
- disp_error_message("parse_simpleexpr: unexpected end of expression",p);
+
+ if (*p == ';' || *p == ',') {
+ disp_error_message("script:parse_simpleexpr: unexpected end of expression", p);
+ }
+
if (*p == '(') {
return script->parse_simpleexpr_paren(p);
} else if (is_number(p)) {
return script->parse_simpleexpr_number(p);
} else if(*p == '"') {
+ const char *p2 = p + 1;
+
+ while (*p2 != '"') {
+ ++p2;
+ }
+
+ if (p2[1] == ':' && p2[2] == ':') {
+ return script->parse_callfunc(p, 1, 0); // XXX: why does callfunc use int for booleans?
+ }
+
return script->parse_simpleexpr_string(p);
} else {
return script->parse_simpleexpr_name(p);
@@ -1577,6 +1669,85 @@ static const char *parse_line(const char *p)
return p;
}
+/**
+ * parses a local function expression
+ *
+ * expects these formats:
+ * function <name>;
+ * function <name> { <script> }
+ *
+ * this is invoked by script->parse_syntax() after checking whether the function
+ * is public or not
+ *
+ * @param p - a pointer to the start of the function expression
+ * @param is_public - whether this function should be accessible from outside the NPC scope
+ */
+static const char *parse_syntax_function (const char *p, bool is_public)
+{
+ const char *func_name = script->skip_space(p); // the name of the local function
+ p = script->skip_word(func_name);
+
+ if (p == func_name) {
+ disp_error_message("script:parse_syntax_function: function name is missing or invalid", p);
+ }
+
+ const char *p2 = script->skip_space(p);
+
+ if (*p2 == ';') {
+ // function <name> ;
+ // function declaration - just register the name
+ int l = script->add_word(func_name);
+
+ if (script->str_data[l].type == C_NOP) {
+ // register only, if the name was not used by something else
+ script->str_data[l].type = C_USERFUNC;
+ } else if (script->str_data[l].type != C_USERFUNC) {
+ disp_error_message("script:parse_syntax_function: function name is already in use", func_name);
+ }
+
+ // Close condition of if, for, while
+ p = script->parse_syntax_close(p2 + 1);
+ return p;
+ } else if (*p2 == '{') {
+ // function <name> <line/block of code>
+ script->syntax.curly[script->syntax.curly_count].type = TYPE_USERFUNC;
+ script->syntax.curly[script->syntax.curly_count].count = 1;
+ script->syntax.curly[script->syntax.curly_count].index = script->syntax.index++;
+ script->syntax.curly[script->syntax.curly_count].flag = 0;
+ ++script->syntax.curly_count;
+
+ // Jump over the function code
+ char label[256];
+ sprintf(label, "goto __FN%x_FIN;", (unsigned int)script->syntax.curly[script->syntax.curly_count - 1].index);
+ script->syntax.curly[script->syntax.curly_count].type = TYPE_NULL;
+ ++script->syntax.curly_count;
+ script->parse_line(label);
+ --script->syntax.curly_count;
+
+ // Set the position of the function (label)
+ int l = script->add_word(func_name);
+
+ if (script->str_data[l].type == C_NOP || script->str_data[l].type == C_USERFUNC) {
+ // register only, if the name was not used by something else
+ script->str_data[l].type = C_USERFUNC;
+ script->set_label(l, VECTOR_LENGTH(script->buf), p);
+
+ if ((script->parse_options & SCRIPT_USE_LABEL_DB) != 0) {
+ script->label_add(l, VECTOR_LENGTH(script->buf),
+ LABEL_IS_USERFUNC | (is_public ? LABEL_IS_EXTERN : 0));
+ }
+ } else {
+ disp_error_message("script:parse_syntax_function: function name is already in use", func_name);
+ }
+
+ return script->skip_space(p);
+ } else {
+ disp_error_message("script:parse_syntax_function: expected ';' or '{' at function syntax", p);
+ }
+
+ return p;
+}
+
// { ... } Closing process
static const char *parse_curly_close(const char *p)
{
@@ -1920,65 +2091,11 @@ static const char *parse_syntax(const char *p)
script->set_label(l, VECTOR_LENGTH(script->buf), p);
return p;
} else if( p2 - p == 8 && strncmp(p, "function", 8) == 0 ) {
- // internal script function
- const char *func_name;
-
- func_name = script->skip_space(p2);
- p = script->skip_word(func_name);
- if( p == func_name )
- disp_error_message("parse_syntax:function: function name is missing or invalid", p);
- p2 = script->skip_space(p);
- if( *p2 == ';' )
- {// function <name> ;
- // function declaration - just register the name
- int l;
- l = script->add_word(func_name);
- if( script->str_data[l].type == C_NOP )// register only, if the name was not used by something else
- script->str_data[l].type = C_USERFUNC;
- else if( script->str_data[l].type == C_USERFUNC )
- ; // already registered
- else
- disp_error_message("parse_syntax:function: function name is invalid", func_name);
-
- // Close condition of if, for, while
- p = script->parse_syntax_close(p2 + 1);
- return p;
- }
- else if(*p2 == '{')
- {// function <name> <line/block of code>
- char label[256];
- int l;
-
- script->syntax.curly[script->syntax.curly_count].type = TYPE_USERFUNC;
- script->syntax.curly[script->syntax.curly_count].count = 1;
- script->syntax.curly[script->syntax.curly_count].index = script->syntax.index++;
- script->syntax.curly[script->syntax.curly_count].flag = 0;
- ++script->syntax.curly_count;
-
- // Jump over the function code
- sprintf(label, "goto __FN%x_FIN;", (unsigned int)script->syntax.curly[script->syntax.curly_count-1].index);
- script->syntax.curly[script->syntax.curly_count].type = TYPE_NULL;
- ++script->syntax.curly_count;
- script->parse_line(label);
- --script->syntax.curly_count;
-
- // Set the position of the function (label)
- l=script->add_word(func_name);
- if( script->str_data[l].type == C_NOP || script->str_data[l].type == C_USERFUNC )// register only, if the name was not used by something else
- {
- script->str_data[l].type = C_USERFUNC;
- script->set_label(l, VECTOR_LENGTH(script->buf), p);
- if( script->parse_options&SCRIPT_USE_LABEL_DB )
- script->label_add(l, VECTOR_LENGTH(script->buf));
- }
- else
- disp_error_message("parse_syntax:function: function name is invalid", func_name);
-
- return script->skip_space(p);
- }
- else
- {
- disp_error_message("expect ';' or '{' at function syntax",p);
+ // local function not marked as public or private
+ if (script->config.functions_private_by_default) {
+ return script->parse_syntax_function(p2, false);
+ } else {
+ return script->parse_syntax_function(p2, true);
}
}
break;
@@ -2006,6 +2123,26 @@ static const char *parse_syntax(const char *p)
return p;
}
break;
+ case 'p':
+ case 'P':
+ if (p2 - p == 6 && strncmp(p, "public", 6) == 0) {
+ p2 = script->skip_space(p2);
+ const char *p3 = script->skip_word(p2);
+
+ if (p3 - p2 == 8 && strncmp(p2, "function", 8) == 0) {
+ // local function explicitly marked as public
+ return script->parse_syntax_function(p3, true);
+ }
+ } else if (p2 - p == 7 && strncmp(p, "private", 7) == 0) {
+ p2 = script->skip_space(p2);
+ const char *p3 = script->skip_word(p2);
+
+ if (p3 - p2 == 8 && strncmp(p2, "function", 8) == 0) {
+ // local function explicitly marked as private
+ return script->parse_syntax_function(p3, false);
+ }
+ }
+ break;
case 's':
case 'S':
if( p2 - p == 6 && strncmp(p, "switch", 6) == 0 ) {
@@ -2668,25 +2805,32 @@ static struct script_code *parse_script(const char *src, const char *file, int l
}
}
- while( script->syntax.curly_count != 0 || *p != end )
- {
- if( *p == '\0' )
- disp_error_message("unexpected end of script",p);
+ while (script->syntax.curly_count != 0 || *p != end) {
+ if (*p == '\0') {
+ disp_error_message("script:parse_script: unexpected end of script", p);
+ }
+
// Special handling only label
- tmpp=script->skip_space(script->skip_word(p));
- if(*tmpp==':' && !(strncmp(p,"default:",8) == 0 && p + 7 == tmpp)) {
- i=script->add_word(p);
+ tmpp = script->skip_space(script->skip_word(p));
+
+ if (*tmpp == ':' && !(strncmp(p, "default:", 8) == 0 && p + 7 == tmpp)
+ && !(strncmp(p, "function", 8) == 0 && script->skip_space(p + 8) == tmpp)) {
+ i = script->add_word(p);
script->set_label(i, VECTOR_LENGTH(script->buf), p);
- if( script->parse_options&SCRIPT_USE_LABEL_DB )
- script->label_add(i, VECTOR_LENGTH(script->buf));
- p=tmpp+1;
- p=script->skip_space(p);
+
+ if ((script->parse_options & SCRIPT_USE_LABEL_DB) != 0) {
+ bool is_extern = ((p[0] == 'O' || p[0] == 'o') && (p[1] == 'N' || p[1] == 'n'));
+ script->label_add(i, VECTOR_LENGTH(script->buf), is_extern ? LABEL_IS_EXTERN : 0);
+ }
+
+ p = tmpp + 1;
+ p = script->skip_space(p);
continue;
}
// All other lumped
- p=script->parse_line(p);
- p=script->skip_space(p);
+ p = script->parse_line(p);
+ p = script->skip_space(p);
script->parse_nextline(false, p);
}
@@ -4866,6 +5010,8 @@ static bool script_config_read(const char *filename, bool imported)
libconfig->setting_lookup_bool_real(setting, "warn_func_mismatch_paramnum", &script->config.warn_func_mismatch_paramnum);
libconfig->setting_lookup_bool_real(setting, "warn_func_mismatch_argtypes", &script->config.warn_func_mismatch_argtypes);
+ libconfig->setting_lookup_bool_real(setting, "functions_private_by_default", &script->config.functions_private_by_default);
+ libconfig->setting_lookup_bool_real(setting, "functions_as_events", &script->config.functions_as_events);
libconfig->setting_lookup_int(setting, "check_cmdcount", &script->config.check_cmdcount);
libconfig->setting_lookup_int(setting, "check_gotocount", &script->config.check_gotocount);
libconfig->setting_lookup_int(setting, "input_min_value", &script->config.input_min_value);
@@ -6435,6 +6581,111 @@ static BUILDIN(callfunc)
return true;
}
+
+/**
+ * Calls a local function within a NPC as if it was part of the current scope.
+ * Resumes execution in the previous scope once the NPC function returns. This
+ * is essentially a clone of buildin_callsub that can run in arbitrary NPCs.
+ *
+ * Usage:
+ * callfunctionofnpc("<npc name>", "<function name>"{, <arg>...})
+ * callfunctionofnpc(<npc id>, "<function name>"{, <arg>...})
+ *
+ * This buildin is also used internally by this syntax:
+ * "<npc name>"::<function name>({<arg>...})
+ */
+static BUILDIN(callfunctionofnpc) {
+ struct npc_data *nd = NULL;
+
+ if (script_isstring(st, 2)) {
+ nd = npc->name2id(script_getstr(st, 2));
+ } else {
+ nd = map->id2nd(script_getnum(st, 2));
+ }
+
+ if (nd == NULL) {
+ ShowError("script:callfunctionofnpc: NPC not found.\n");
+ st->state = END;
+ return false;
+ }
+
+ const char *function_name = script_getstr(st, 3);
+ int pos = -1;
+
+ // find the function label within the label list of the NPC
+ for (int i = 0; i < nd->u.scr.label_list_num; ++i) {
+ if (strcmp(nd->u.scr.label_list[i].name, function_name) == 0) {
+ if ((nd->u.scr.label_list[i].flags & LABEL_IS_EXTERN) != 0
+ && (nd->u.scr.label_list[i].flags & LABEL_IS_USERFUNC) != 0) {
+ // function label found: set the start location
+ pos = nd->u.scr.label_list[i].pos;
+ } else if ((nd->u.scr.label_list[i].flags & LABEL_IS_USERFUNC) != 0) {
+ ShowError("script:callfunctionofnpc: function '%s' is not marked as public in NPC '%s'.\n", function_name, nd->name);
+ st->state = END;
+ return false;
+ }
+ break;
+ }
+ }
+
+ if (pos < 0) {
+ ShowError("script:callfunctionofnpc: function '%s' not found in NPC '%s'!\n", function_name, nd->name);
+ st->state = END;
+ return false;
+ }
+
+ // alloc a reg_db reference of the current scope for the new scope
+ struct reg_db *ref = (struct reg_db *)aCalloc(sizeof(struct reg_db), 2);
+ // scope variables (.@var)
+ ref[0].vars = st->stack->scope.vars;
+ ref[0].arrays = st->stack->scope.arrays;
+ // npc variables (.var)
+ ref[1].vars = st->script->local.vars;
+ ref[1].arrays = st->script->local.arrays;
+
+ int i = 0;
+
+ // make sure the arguments we push retain their current reg_db references:
+ // this allows to do things like set(getarg(0), ...)
+ for (i = st->start + 4; i < st->end; i++) {
+ struct script_data *data = script->push_copy(st->stack, i);
+
+ if (data_isreference(data) && data->ref == NULL) {
+ const char *name = reference_getname(data);
+
+ if (name[0] == '.') {
+ data->ref = (name[1] == '@' ? &ref[0] : &ref[1]);
+ }
+ }
+ }
+
+ // save the previous scope
+ struct script_retinfo *ri = NULL;
+ CREATE(ri, struct script_retinfo, 1);
+ ri->script = st->script; // script code
+ ri->scope.vars = st->stack->scope.vars; // scope variables
+ ri->scope.arrays = st->stack->scope.arrays; // scope arrays
+ ri->pos = st->pos; // script location
+ ri->nargs = i - st->start - 4; // argument count
+ ri->defsp = st->stack->defsp; // default stack pointer
+ script->push_retinfo(st->stack, ri, ref);
+
+ // change the current scope to the scope of the function
+ st->pos = pos;
+ st->script = nd->u.scr.script;
+ st->stack->defsp = st->stack->sp;
+ st->state = GOTO;
+ st->stack->scope.vars = i64db_alloc(DB_OPT_RELEASE_DATA);
+ st->stack->scope.arrays = idb_alloc(DB_OPT_BASE);
+
+ // make sure local reg_db of the other NPC is initialized
+ if (st->script->local.vars == NULL) {
+ st->script->local.vars = i64db_alloc(DB_OPT_RELEASE_DATA);
+ }
+
+ return true;
+}
+
/*==========================================
* subroutine call
*------------------------------------------*/
@@ -27428,6 +27679,8 @@ static void script_parse_builtin(void)
BUILDIN_DEF(identify, "i"),
BUILDIN_DEF(identifyidx, "i"),
BUILDIN_DEF(openlapineddukddakboxui, "i"),
+
+ BUILDIN_DEF(callfunctionofnpc, "vs*"),
};
int i, len = ARRAYLENGTH(BUILDIN);
RECREATE(script->buildin, char *, script->buildin_count + len); // Pre-alloc to speed up
@@ -27439,7 +27692,7 @@ static void script_parse_builtin(void)
#undef BUILDIN_DEF
#undef BUILDIN_DEF2
-static void script_label_add(int key, int pos)
+static void script_label_add(int key, int pos, enum script_label_flags flags)
{
int idx = script->label_count;
@@ -27450,6 +27703,7 @@ static void script_label_add(int key, int pos)
script->labels[idx].key = key;
script->labels[idx].pos = pos;
+ script->labels[idx].flags = flags;
script->label_count++;
}
@@ -28261,6 +28515,7 @@ void script_defaults(void)
script->parse_syntax_close = parse_syntax_close;
script->parse_syntax_close_sub = parse_syntax_close_sub;
script->parse_syntax = parse_syntax;
+ script->parse_syntax_function = parse_syntax_function;
script->get_com = get_com;
script->get_num = get_num;
script->op2name = script_op2name;
@@ -28365,6 +28620,8 @@ void script_defaults(void)
script->config.ontouch_name = "OnTouch_"; //ontouch_name (runs on first visible char to enter area, picks another char if the first char leaves)
script->config.ontouch2_name = "OnTouch"; //ontouch2_name (run whenever a char walks into the OnTouch area)
script->config.onuntouch_name = "OnUnTouch"; //onuntouch_name (run whenever a char walks from the OnTouch area)
+ script->config.functions_private_by_default = true;
+ script->config.functions_as_events = false;
// for ENABLE_CASE_CHECK
script->calc_hash_ci = calc_hash_ci;
diff --git a/src/map/script.h b/src/map/script.h
index 5fa81dc0e..df5297ac0 100644
--- a/src/map/script.h
+++ b/src/map/script.h
@@ -584,6 +584,8 @@ enum itemskill_flag {
struct Script_Config {
bool warn_func_mismatch_argtypes;
bool warn_func_mismatch_paramnum;
+ bool functions_private_by_default;
+ bool functions_as_events;
int check_cmdcount;
int check_gotocount;
int input_min_value;
@@ -725,8 +727,14 @@ struct str_data_struct {
uint8 deprecated : 1;
};
+/** a label within a script (does not use the label db) */
struct script_label_entry {
- int key,pos;
+ /** label name (held within str_data) */
+ int key;
+ /** position within the script */
+ int pos;
+ /** optional flags for the label */
+ enum script_label_flags flags;
};
struct script_syntax_data {
@@ -917,7 +925,7 @@ struct script_interface {
void (*set_constant) (const char *name, int value, bool is_parameter, bool is_deprecated);
void (*set_constant2) (const char *name, int value, bool is_parameter, bool is_deprecated);
bool (*get_constant) (const char* name, int* value);
- void (*label_add)(int key, int pos);
+ void (*label_add)(int key, int pos, enum script_label_flags flags);
void (*run) (struct script_code *rootscript, int pos, int rid, int oid);
void (*run_npc) (struct script_code *rootscript, int pos, int rid, int oid);
void (*run_pet) (struct script_code *rootscript, int pos, int rid, int oid);
@@ -948,10 +956,11 @@ struct script_interface {
int (*queue_create) (void);
bool (*queue_clear) (int idx);
/* */
- const char * (*parse_curly_close) (const char *p);
- const char * (*parse_syntax_close) (const char *p);
- const char * (*parse_syntax_close_sub) (const char *p, int *flag);
- const char * (*parse_syntax) (const char *p);
+ const char *(*parse_curly_close) (const char *p);
+ const char *(*parse_syntax_close) (const char *p);
+ const char *(*parse_syntax_close_sub) (const char *p, int *flag);
+ const char *(*parse_syntax) (const char *p);
+ const char *(*parse_syntax_function) (const char *p, bool is_public);
c_op (*get_com) (const struct script_buf *scriptbuf, int *pos);
int (*get_num) (const struct script_buf *scriptbuf, int *pos);
const char* (*op2name) (int op);
diff --git a/src/plugins/HPMHooking/HPMHooking.Defs.inc b/src/plugins/HPMHooking/HPMHooking.Defs.inc
index ef71f1967..d20766956 100644
--- a/src/plugins/HPMHooking/HPMHooking.Defs.inc
+++ b/src/plugins/HPMHooking/HPMHooking.Defs.inc
@@ -6858,8 +6858,8 @@ typedef void (*HPMHOOK_pre_script_set_constant2) (const char **name, int *value,
typedef void (*HPMHOOK_post_script_set_constant2) (const char *name, int value, bool is_parameter, bool is_deprecated);
typedef bool (*HPMHOOK_pre_script_get_constant) (const char **name, int **value);
typedef bool (*HPMHOOK_post_script_get_constant) (bool retVal___, const char *name, int *value);
-typedef void (*HPMHOOK_pre_script_label_add) (int *key, int *pos);
-typedef void (*HPMHOOK_post_script_label_add) (int key, int pos);
+typedef void (*HPMHOOK_pre_script_label_add) (int *key, int *pos, enum script_label_flags *flags);
+typedef void (*HPMHOOK_post_script_label_add) (int key, int pos, enum script_label_flags flags);
typedef void (*HPMHOOK_pre_script_run) (struct script_code **rootscript, int *pos, int *rid, int *oid);
typedef void (*HPMHOOK_post_script_run) (struct script_code *rootscript, int pos, int rid, int oid);
typedef void (*HPMHOOK_pre_script_run_npc) (struct script_code **rootscript, int *pos, int *rid, int *oid);
@@ -6924,6 +6924,8 @@ typedef const char* (*HPMHOOK_pre_script_parse_syntax_close_sub) (const char **p
typedef const char* (*HPMHOOK_post_script_parse_syntax_close_sub) (const char* retVal___, const char *p, int *flag);
typedef const char* (*HPMHOOK_pre_script_parse_syntax) (const char **p);
typedef const char* (*HPMHOOK_post_script_parse_syntax) (const char* retVal___, const char *p);
+typedef const char* (*HPMHOOK_pre_script_parse_syntax_function) (const char **p, bool *is_public);
+typedef const char* (*HPMHOOK_post_script_parse_syntax_function) (const char* retVal___, const char *p, bool is_public);
typedef c_op (*HPMHOOK_pre_script_get_com) (const struct script_buf **scriptbuf, int **pos);
typedef c_op (*HPMHOOK_post_script_get_com) (c_op retVal___, const struct script_buf *scriptbuf, int *pos);
typedef int (*HPMHOOK_pre_script_get_num) (const struct script_buf **scriptbuf, int **pos);
diff --git a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc
index e8cb41240..02ba063ed 100644
--- a/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc
+++ b/src/plugins/HPMHooking/HPMHooking_map.HPMHooksCore.inc
@@ -5454,6 +5454,8 @@ struct {
struct HPMHookPoint *HP_script_parse_syntax_close_sub_post;
struct HPMHookPoint *HP_script_parse_syntax_pre;
struct HPMHookPoint *HP_script_parse_syntax_post;
+ struct HPMHookPoint *HP_script_parse_syntax_function_pre;
+ struct HPMHookPoint *HP_script_parse_syntax_function_post;
struct HPMHookPoint *HP_script_get_com_pre;
struct HPMHookPoint *HP_script_get_com_post;
struct HPMHookPoint *HP_script_get_num_pre;
@@ -12355,6 +12357,8 @@ struct {
int HP_script_parse_syntax_close_sub_post;
int HP_script_parse_syntax_pre;
int HP_script_parse_syntax_post;
+ int HP_script_parse_syntax_function_pre;
+ int HP_script_parse_syntax_function_post;
int HP_script_get_com_pre;
int HP_script_get_com_post;
int HP_script_get_num_pre;
diff --git a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc
index 6b89841ad..4a36c5a1e 100644
--- a/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc
+++ b/src/plugins/HPMHooking/HPMHooking_map.HookingPoints.inc
@@ -2793,6 +2793,7 @@ struct HookingPointData HookingPoints[] = {
{ HP_POP(script->parse_syntax_close, HP_script_parse_syntax_close) },
{ HP_POP(script->parse_syntax_close_sub, HP_script_parse_syntax_close_sub) },
{ HP_POP(script->parse_syntax, HP_script_parse_syntax) },
+ { HP_POP(script->parse_syntax_function, HP_script_parse_syntax_function) },
{ HP_POP(script->get_com, HP_script_get_com) },
{ HP_POP(script->get_num, HP_script_get_num) },
{ HP_POP(script->op2name, HP_script_op2name) },
diff --git a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc
index 02d55228e..f0e395b64 100644
--- a/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc
+++ b/src/plugins/HPMHooking/HPMHooking_map.Hooks.inc
@@ -71749,14 +71749,14 @@ bool HP_script_get_constant(const char *name, int *value) {
}
return retVal___;
}
-void HP_script_label_add(int key, int pos) {
+void HP_script_label_add(int key, int pos, enum script_label_flags flags) {
int hIndex = 0;
if (HPMHooks.count.HP_script_label_add_pre > 0) {
- void (*preHookFunc) (int *key, int *pos);
+ void (*preHookFunc) (int *key, int *pos, enum script_label_flags *flags);
*HPMforce_return = false;
for (hIndex = 0; hIndex < HPMHooks.count.HP_script_label_add_pre; hIndex++) {
preHookFunc = HPMHooks.list.HP_script_label_add_pre[hIndex].func;
- preHookFunc(&key, &pos);
+ preHookFunc(&key, &pos, &flags);
}
if (*HPMforce_return) {
*HPMforce_return = false;
@@ -71764,13 +71764,13 @@ void HP_script_label_add(int key, int pos) {
}
}
{
- HPMHooks.source.script.label_add(key, pos);
+ HPMHooks.source.script.label_add(key, pos, flags);
}
if (HPMHooks.count.HP_script_label_add_post > 0) {
- void (*postHookFunc) (int key, int pos);
+ void (*postHookFunc) (int key, int pos, enum script_label_flags flags);
for (hIndex = 0; hIndex < HPMHooks.count.HP_script_label_add_post; hIndex++) {
postHookFunc = HPMHooks.list.HP_script_label_add_post[hIndex].func;
- postHookFunc(key, pos);
+ postHookFunc(key, pos, flags);
}
}
return;
@@ -72625,6 +72625,33 @@ const char* HP_script_parse_syntax(const char *p) {
}
return retVal___;
}
+const char* HP_script_parse_syntax_function(const char *p, bool is_public) {
+ int hIndex = 0;
+ const char* retVal___ = NULL;
+ if (HPMHooks.count.HP_script_parse_syntax_function_pre > 0) {
+ const char* (*preHookFunc) (const char **p, bool *is_public);
+ *HPMforce_return = false;
+ for (hIndex = 0; hIndex < HPMHooks.count.HP_script_parse_syntax_function_pre; hIndex++) {
+ preHookFunc = HPMHooks.list.HP_script_parse_syntax_function_pre[hIndex].func;
+ retVal___ = preHookFunc(&p, &is_public);
+ }
+ if (*HPMforce_return) {
+ *HPMforce_return = false;
+ return retVal___;
+ }
+ }
+ {
+ retVal___ = HPMHooks.source.script.parse_syntax_function(p, is_public);
+ }
+ if (HPMHooks.count.HP_script_parse_syntax_function_post > 0) {
+ const char* (*postHookFunc) (const char* retVal___, const char *p, bool is_public);
+ for (hIndex = 0; hIndex < HPMHooks.count.HP_script_parse_syntax_function_post; hIndex++) {
+ postHookFunc = HPMHooks.list.HP_script_parse_syntax_function_post[hIndex].func;
+ retVal___ = postHookFunc(retVal___, p, is_public);
+ }
+ }
+ return retVal___;
+}
c_op HP_script_get_com(const struct script_buf *scriptbuf, int *pos) {
int hIndex = 0;
c_op retVal___ = C_NOP;