From 9bfeab66e105e8a4cd7e54c15b41cff9f68c4d30 Mon Sep 17 00:00:00 2001 From: Atemo Date: Tue, 12 May 2026 14:31:15 +0200 Subject: [PATCH] Archive - Testing the implementation for learning purposes of a "dictionary" format style in rAthena script engine, using an AI agent --- doc/script_commands.txt | 524 +- npc/scripts_test.conf | 7 + npc/test/callnpcsub_multireturn_tests.txt | 189 + npc/test/mdarray_dyn_dict_only_tests.txt | 293 + .../mdarray_dyn_empty_assignment_tests.txt | 112 + .../mdarray_dyn_literal_empty_table_tests.txt | 243 + .../mdarray_dyn_literal_integer_key_tests.txt | 146 + npc/test/mdarray_dyn_string_debug_tests.txt | 195 + src/map/script.cpp | 5526 ++++++++++++----- src/map/script.hpp | 2 + 10 files changed, 5745 insertions(+), 1492 deletions(-) create mode 100644 npc/test/callnpcsub_multireturn_tests.txt create mode 100644 npc/test/mdarray_dyn_dict_only_tests.txt create mode 100644 npc/test/mdarray_dyn_empty_assignment_tests.txt create mode 100644 npc/test/mdarray_dyn_literal_empty_table_tests.txt create mode 100644 npc/test/mdarray_dyn_literal_integer_key_tests.txt create mode 100644 npc/test/mdarray_dyn_string_debug_tests.txt diff --git a/doc/script_commands.txt b/doc/script_commands.txt index 18bca372d74..738b91c2ad5 100644 --- a/doc/script_commands.txt +++ b/doc/script_commands.txt @@ -581,6 +581,37 @@ variables. Once you set it to that, the variable is as good as forgotten forever, and no trace remains of it even if it was stored with character or account data. +Dynamic variables +----------------- + +A variable can be declared as dynamic with the 'dyn' command. Dynamic variables +are intended for temporary script data that may contain both integer and string +values under the same base variable, including multidimensional dictionary-like +arrays. + +Example: + dyn .@user; + .@user["name"] = "Bob"; + .@user["age"] = 20; + +Unlike normal variables, the value type of each dynamic element is decided by +the value assigned to that element. A dynamic variable does not need the '$' +postfix to store strings. + +Dynamic variables currently only support temporary scopes: + .@name - scope dynamic variable + @name - temporary character dynamic variable + .name - NPC dynamic variable + $@name - temporary global dynamic variable + 'name - instance variable + +Permanent variables ('name', '$name', '#name', '##name') should not be used as +dynamic multidimensional arrays unless persistence support has explicitly been +implemented for them. + +Reading a missing dynamic value returns 0. Use 'getarraykeys' or +'getarraysize' to test which dictionary entries actually exist. + Some variables are special, that is, they are already defined for you by the scripting engine. You can see the full list in 'src/map/script_constants.hpp', which is a file you should read, since it also allows you to replace lots of numbered @@ -672,6 +703,14 @@ Note: !! In order to copy arrays between variables the use of 'copyarray' function is still !! required. +Dynamic dictionaries declared with 'dyn' support assigning the empty dictionary +literal '[]' to create or clear a branch: + + dyn .@a; + .@a["list"] = []; + +Other dynamic literals are normally used with 'dyn = [ ... ];'. + Strings ------- @@ -687,7 +726,8 @@ same name. You can tell between the specific variables of an array with an [] -All variable types can be used as arrays. +All variable types can be used as arrays. Integer arrays return 0 for missing +elements and string arrays return an empty string. Variables stored in this way, inside an array, are also called 'array elements'. Arrays are specifically useful for storing a set of similar data (like several @@ -718,6 +758,91 @@ Arrays can naturally store strings: the '$', normally denoting a string variable, before the square brackets that denotes an array index. + +Dynamic dictionary variables +---------------------------- + +Traditional rAthena arrays remain one-dimensional and keep the legacy behavior +where a variable without an explicit index refers to index 0: + + .@a = 1; + .@a[0] == .@a; // true for legacy arrays + +Dynamic dictionaries are a separate runtime storage declared with the 'dyn' +command. They are intended for temporary, dictionary-like data that may contain +integer values, string values, empty dictionaries and nested dictionaries under +the same base variable. + + dyn .@user; + .@user["name"] = "Bob"; + .@user["age"] = 20; + .@user["items"][0]["id"] = 501; + +Dynamic variables are dictionaries only. The root itself is not a scalar value, +and assigning a scalar directly to a dynamic variable is an error: + + dyn .@a; + .@a = 1; // error + +Use an indexed path to store values: + + .@a[0] = 1; + .@a["key"] = "value"; + +Dynamic dictionaries are sparse and non-rectangular. A path may exist without +requiring any sibling or parent scalar value to exist: + + dyn .@a; + .@a[1] = 5; + .@a[2][101] = 7; + .@a["config"]["map"] = "prt_gld"; + +Keys are typed. Integer key 0 and string key "0" are different keys: + + dyn .@a; + .@a[0] = "integer key"; + .@a["0"] = "string key"; + +Empty dictionary branches can be created or cleared with the empty dynamic +literal '[]': + + dyn .@a; + .@a["list"] = []; + getarraysizedyn(.@a["list"]); // returns 0 + +Dynamic literals can be used to initialize large dictionary structures. They use +square brackets with key/value pairs. Keys may be string literals, non-negative +integer literals or parenthesized expressions that evaluate to a string or +non-negative integer key. + + dyn .data = [ + "Prontera": [ + "prtg_cas01": [ + "agitend": "Agit#prtg_cas01::OnAgitEnd", + "eventkillmob": "Agit#prtg_cas01::OnAgitBreak", + "warp": [ + "map": "prt_gld", + "x": 134, + "y": 65, + ], + ], + ], + ]; + + .@region$ = "Prontera"; + dyn .@cfg = [ + (.@region$): [ + 0: "integer child", + "0": "string child", + ], + ]; + +A missing dynamic leaf reads as the default value 0. Reading a missing path does +not create it. Use 'getarraykeys', 'getarraysizedyn' or 'getdyntype' to inspect +which dictionary branches actually exist. + +Use 'del' to remove a dynamic key or branch. + Variable References ------------------- @@ -1402,6 +1527,164 @@ Examples: --------------------------------------- +*dyn ; +*dyn = []; + +Declares a variable as dynamic. Dynamic variables are dictionary-only values and +are separate from legacy one-dimensional arrays. A dynamic variable does not use +the '$' postfix to decide the type of its values; each leaf value keeps the type +of the value assigned to it. + +Only root variables can be declared dynamic. Indexed declarations are invalid: + + dyn .@a; // OK + dyn .@a[0]; // error + dyn .@a["x"]; // error + +Supported temporary scopes are: + .@var, @var, .var, $@var, 'var + +Permanent variables are not supported as dynamic dictionaries unless persistence +support has explicitly been implemented for them. + +If the variable is not already dynamic, 'dyn' clears any previous legacy array +content and creates an empty dynamic dictionary. If the variable is already +dynamic, 'dyn ;' is idempotent and does not clear it. + + .@a = 1; + dyn .@a; + getdyntype(.@a); // 1 + getarraysizedyn(.@a); // 0 + + dyn .@a; + .@a["x"] = 1; + dyn .@a; // does not clear .@a + .@a["x"]; // still 1 + +When a dynamic literal is supplied, the previous dynamic dictionary is replaced: + + dyn .@a; + .@a["old"] = 1; + dyn .@a = [ + "new": 2, + ]; + .@a["old"]; // 0 + .@a["new"]; // 2 + +Dynamic variables are dictionaries only. Direct scalar assignment to the root is +an error: + + dyn .@a; + .@a = 1; // error + +Store values under keys instead: + + .@a[0] = 1; + .@a["name"] = "Bob"; + +Dynamic literal syntax: + + dyn .@a = [ + "string_key": "value", + 0: "integer key", + 0x10: "hex integer key", + (.@expr$): "expression key", + "empty": [], + ]; + +Integer literal keys must be non-negative. Parenthesized key expressions must +evaluate to either a string key or a non-negative integer key. + +--------------------------------------- + +*setarraydyn ,{,...}; + +Writes a sequence of values under a dynamic dictionary branch using consecutive +integer keys. The target must be dynamic. Use this for dynamic dictionaries; use +'setarray' for legacy one-dimensional arrays. + + dyn .@a; + setarraydyn .@a["list"], "a", "b", "c"; + +This is equivalent to: + + .@a["list"][0] = "a"; + .@a["list"][1] = "b"; + .@a["list"][2] = "c"; + +A starting numeric child key may be supplied: + + setarraydyn .@a["list"][5], "x", "y"; + +This writes: + + .@a["list"][5] = "x"; + .@a["list"][6] = "y"; + +To create or clear an empty dictionary branch, assign the empty literal '[]': + + .@a["list"] = []; + +--------------------------------------- + +*getarraysizedyn() + +Returns the number of direct keys under a dynamic dictionary path. The target +must be dynamic. Use 'getarraysize' for legacy one-dimensional arrays. + + dyn .@a; + .@a[1] = 5; + .@a[2][101] = 7; + getarraysizedyn(.@a); // returns 2: keys 1 and 2 + getarraysizedyn(.@a[2]); // returns 1: key 101 + +Empty dictionary branches have size 0: + + .@a["empty"] = []; + getarraysizedyn(.@a["empty"]); // returns 0 + +--------------------------------------- + +*getdyntype() + +Returns whether a variable is dynamic. + +Return values: + 0 - not dynamic + 1 - dynamic dictionary + +Examples: + + .@a = 1; + getdyntype(.@a); // 0 + + dyn .@a; + getdyntype(.@a); // 1 + +--------------------------------------- + +*del ; + +Deletes a dynamic dictionary branch or leaf. Deleting a missing key is allowed +and does nothing. + + dyn .@a; + .@a["list"] = "a"; + .@a["truc"] = "b"; + del .@a["truc"]; + +After this, only the 'list' key remains under .@a. + +Deleting the root dynamic variable removes the dynamic dictionary itself. After +that, getdyntype() returns 0 for that variable. + + dyn .@a; + .@a["x"] = 1; + del .@a; + getdyntype(.@a); // 0 + +--------------------------------------- + *getvariableofnpc(,"") Returns a reference to a NPC variable (. prefix) from the target NPC. @@ -1730,6 +2013,21 @@ Note: !! call it. That is to say, any functions should be placed above scripts or NPCs !! (or loaded in a separate file first) before attempting to call them directly. + +Multiple return values are supported through 'return' and can be read with +'getreturn' and 'getreturncount'. When a function is used in an expression, only +the first returned value is used for compatibility. + + function%TAB%script%TAB%F_GetInfo%TAB%{ + return "Bob", 20, "Knight"; + } + + .@name$ = callfunc("F_GetInfo"); // receives only "Bob" + callfunc "F_GetInfo"; + debugmes getreturn(0); // Bob + debugmes getreturn(1); // 20 + debugmes getreturn(2); // Knight + --------------------------------------- *callsub