Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extend is_function() with support for public local functions #2742

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions doc/script_commands.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2040,16 +2040,19 @@ Example:

---------------------------------------

*is_function("<function name>")
*is_function("<function name>"{, <npc id>})
*is_function("<function name>"{, "<npc name>"})

This command checks whether or not a function exists and returns its type.
Returns false if it cannot be found.
This command checks whether or not a function exists and returns its type. If a
NPC name or ID is provided, it will try to find the label or function within
this NPC instead of the attached NPC. Returns false if it cannot be found.

return values:

FUNCTION_IS_COMMAND - built-in script command (eg: mes, select, ...)
FUNCTION_IS_GLOBAL - user-defined global function (callable with callfunc)
FUNCTION_IS_LOCAL - user-defined local function
FUNCTION_IS_PUBLIC - user-defined local function marked as public
FUNCTION_IS_LABEL - user-defined label function (callable with callsub)

Example:
Expand Down
16 changes: 16 additions & 0 deletions npc/dev/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ function script F_TestVarOfAnotherNPC {

return set(getarg(0), 1337);
}

OnLabel:
// to test is_function() with the label of another NPC
end;
}

function script HerculesSelfTestHelper {
Expand Down Expand Up @@ -825,6 +829,18 @@ function script HerculesSelfTestHelper {
callsub(OnCheck, "public local function var reference test", .@refTest, 1337);
callsub(OnCheck, "programatic public local call", callfunctionofnpc("export test", "RefTest", .@refTest = 1), 1337);

private function localFunc {}
public function pubLocalFunc {}
callsub(OnCheck, "is_function: not found", is_function("does-not-exist"), false);
callsub(OnCheck, "is_function: built-in script command", is_function("callfunc"), FUNCTION_IS_COMMAND);
callsub(OnCheck, "is_function: global function", is_function("HerculesSelfTestHelper"), FUNCTION_IS_GLOBAL);
callsub(OnCheck, "is_function: local NPC function", is_function("localFunc"), FUNCTION_IS_LOCAL);
callsub(OnCheck, "is_function: public local NPC function", is_function("pubLocalFunc"), FUNCTION_IS_PUBLIC);
callsub(OnCheck, "is_function: local subroutine", is_function("OnCheck"), FUNCTION_IS_LABEL);
callsub(OnCheck, "is_function: local NPC function of another NPC", is_function("Private", "export test"), FUNCTION_IS_LOCAL);
callsub(OnCheck, "is_function: public local NPC function of another NPC", is_function("Public", "export test"), FUNCTION_IS_PUBLIC);
callsub(OnCheck, "is_function: local subroutine of another NPC", is_function("OnLabel", "export test"), FUNCTION_IS_LABEL);

if (.errors) {
consolemes(CONSOLEMES_DEBUG, "Script engine self-test [ \033[0;31mFAILED\033[0m ]");
consolemes(CONSOLEMES_DEBUG, "**** The test was completed with " + .errors + " errors. ****");
Expand Down
55 changes: 50 additions & 5 deletions src/map/script.c
Original file line number Diff line number Diff line change
Expand Up @@ -23900,24 +23900,33 @@ static BUILDIN(getcharip)
return true;
}

/** possible types of script commands */
enum function_type {
/** not found */
FUNCTION_IS_NONE = 0,
/** script buildin */
FUNCTION_IS_COMMAND,
/** global user-defined function (for use with callfunc) */
FUNCTION_IS_GLOBAL,
/** user-defined function local to the NPC */
FUNCTION_IS_LOCAL,
/** user-defined label (for use with callsub) */
FUNCTION_IS_LABEL,
/** user-defined function local to the NPC, marked as public */
FUNCTION_IS_PUBLIC,
};

/**
* is_function(<function name>)
**/
* is_function("<function name>"{, <npc id>})
* is_function("<function name>"{, "<npc name>"})
*
* checks the type of a script command
*/
static BUILDIN(is_function)
{
const char *str = script_getstr(st, 2);
enum function_type type = FUNCTION_IS_NONE;

// TODO: add support for exported functions (#2142)

if (strdb_exists(script->userfunc_db, str)) {
type = FUNCTION_IS_GLOBAL;
} else {
Expand All @@ -23943,6 +23952,41 @@ static BUILDIN(is_function)
}
}
}

if (type == FUNCTION_IS_LOCAL
|| (script_hasdata(st, 3) && type != FUNCTION_IS_COMMAND)) {
struct npc_data *nd = map->id2nd(st->oid);

if (script_hasdata(st, 3)) {
// at this point we know it's not a global or a buildin, so we
// reset since we want to search another NPC
type = FUNCTION_IS_NONE;

if (script_isint(st, 3)) {
nd = map->id2nd(script_getnum(st, 3));
} else if (script_isstring(st, 3)) {
nd = npc->name2id(script_getstr(st, 3));
}
}

if (nd != NULL) {
// try to find the label
for (int i = 0; i < nd->u.scr.label_list_num; ++i) {
if (strcmp(nd->u.scr.label_list[i].name, str) == 0) {
if ((nd->u.scr.label_list[i].flags & LABEL_IS_USERFUNC) != 0) {
if ((nd->u.scr.label_list[i].flags & LABEL_IS_EXTERN) != 0) {
type = FUNCTION_IS_PUBLIC;
} else {
type = FUNCTION_IS_LOCAL;
}
} else {
type = FUNCTION_IS_LABEL;
}
break;
}
}
}
}
}

script_pushint(st, type);
Expand Down Expand Up @@ -27555,7 +27599,7 @@ static void script_parse_builtin(void)
**/
BUILDIN_DEF(getargcount,""),
BUILDIN_DEF(getcharip,"?"),
BUILDIN_DEF(is_function,"s"),
BUILDIN_DEF(is_function,"s?"),
BUILDIN_DEF(freeloop,"i"),
BUILDIN_DEF(getrandgroupitem,"ii"),
BUILDIN_DEF(cleanmap,"s"),
Expand Down Expand Up @@ -28181,6 +28225,7 @@ static void script_hardcoded_constants(void)
script->set_constant("FUNCTION_IS_GLOBAL", FUNCTION_IS_GLOBAL, false, false);
script->set_constant("FUNCTION_IS_LOCAL", FUNCTION_IS_LOCAL, false, false);
script->set_constant("FUNCTION_IS_LABEL", FUNCTION_IS_LABEL, false, false);
script->set_constant("FUNCTION_IS_PUBLIC", FUNCTION_IS_PUBLIC, false, false);

script->constdb_comment("item trade restrictions");
script->set_constant("ITR_NONE", ITR_NONE, false, false);
Expand Down