Hi all,
I think the answer to this is “no” but I thought I’d ask to be sure! Is it possible to have associative arrays in NWN1?
Ideally I’d like to have a multi-dimensional array with an emote name and the animation length for my emote code.
- May
Hi all,
I think the answer to this is “no” but I thought I’d ask to be sure! Is it possible to have associative arrays in NWN1?
Ideally I’d like to have a multi-dimensional array with an emote name and the animation length for my emote code.
Yes you can, it is a bit tricky with json as you need to make an array from a array of 2 elements, but you can also use pre-EE solutions, ie. pseudo arrays with which this is trivial as they allow to store multiple different types of values in one “element”.
Alternatively you can make your own 2DA which is exactly that.
If you’re only planning on having one string key mapping to one numeric value, you could just use local vars on your module. Do you ever need to iterate this collection of emotes, anim lengths pairs?
You could also do this with the sqlite3 stuff too. Over at “nwnx:ee/unified”, they implement an array, main difference is you would be indexing on string instead of an integer and there’s a lot of unneeded abstractions for your particular use case.
Having tried all three methods, I find that psuedo-arrays are the most convenient when the content changes a lot, especially if new rows have to be added while editing conversations and scripts for new quests - it helps to have all the data within the toolset.
A new 2da is worth considering when there’s a lot of data which is fairly stable. It’s easy to cast into neat rows using a 2da editor / spreadsheet. Extracting the 2da into override or hak before testing can become time-consuming if there are frequent changes. Officially, new 2da files are “unsupported” but only because you need to choose a file name that the developers aren’t likely to use, e.g. may_emotes.2da.
Pseudo-arrays are made using functions like SetLocalArrayInt() and so on.
To make the table easier to edit in the toolset, you can used a wrapper fiunction to specify each row. For example, a reward table:
// NPC Transaction Gold XP CheckItems TakeItems GiveItems CreateItems
// Add custom code here, using the template above as an example
zStoreReward("PC", "pnut_box", 0, 0, "", "", "", "ornatebox");
zStoreReward("PC", "find_ring", 0, 0, "", "", "", "fitzwilliamring");
zStoreReward("Firk", "hire", 0, 100, "", "", "", "");
zStoreReward("Firk", "nightwatch", 0, 100, "", "", "", "", EXCLUDE);
zStoreReward("AstonCook", "deliver_meat", 0, 100, "Mutton,Beef,Pork", "Mutton,Beef,Pork", "", "");
The meat of the wrapper function looks like this:
SetLocalInt(oNPC, "Rewards", nReward);
SetLocalArrayString(oNPC, "Transaction", nReward, sTransaction);
SetLocalArrayInt (oNPC, "Gold", nReward, nGold);
SetLocalArrayInt (oNPC, "XP", nReward, nXP);
zStoreItems(oNPC, "CheckItem", nReward, sCheckItems);
zStoreItems(oNPC, "TakeItem", nReward, sTakeItems);
zStoreItems(oNPC, "GiveItem", nReward, sGiveItems);
zStoreItems(oNPC, "CreateItem", nReward, sCreateItems);
This example also supports lists within fields (zStoreItems) as it happens.
In this particular case, the table is stored on NPCs. It’s OK to store tables on the module, but if you have a lot of tables, it’s better to store each table on a dedicated invisible object, so that dm_dumpmodulelocals isn’t too cluttered.
Essentially I have a couple implementations for this I want to do:
firstly a list of “@-commands” and their respective functions, and secondly a list of emotes and their oneshot durations.
I want to for example list over a given @input in chat to see if it’s a supported command (@help, @emote, etc)
I did consider 2das for that but I am given to think from the other thread that they might break in updates. Is that not the case?
I think the only way a 2da would break in an update is if you chose a name that the devs subsequently used for a new game 2da, or you just added your data to an existing game 2da (which would be a mistake in this case anyway).
Correct, so emotes.2da
and commands.2da
are somewhat risky. But mypw_emotes.2da
are very unlikely to break. There’s only been a couple of 2DA additions over the last 6 years (e.g. ruleset.2da
, encoding.2da
)
I think 2das sound like they’re the best way to do this then. Is there anything special I have to do to use my own 2das, or how does implementing my own 2da work exactly? Google search decided that no really I’m using NWN2 and was unhelpful…
Much the same as an official one. Just make a text file in the correct format with the .2da extension.
You read the 2da using Get2DAString().
Lookup is achieved by looping through the 2da. Bear in mind that **** fields return a null string, same as end-of-file.
I guess my thought was if I need something to load them in? Or will the game automagically grab them out of a hak without such prompting?
No. Yes. The first argument to Get2DAString is the filename (less extension). Game tries to open it and I think just returns empty string as well if it fails. No need to tell it before hand about the existence of your file as long as it’s in the hak/override/development/etc. If you map your emotes to numbers lookup can be easier too.
I’m looking at extending this to a sort of lookup table for my chat command parsing in general.
What I want to have is a 2da like this:
What I’m not finding is a way to call a function stored as a string, or at least the things I tried didn’t seem to work properly. How would I go about this? Basically I want to store in the file the name of the commands we’re calling, and then be able to call them after referencing the 2da.
An old way of doing this was to make each function a script, since ExecuteScript() accepts a string argument.
Now you can have one script, passing the function name (or id) required via SetScriptParam(). That doesn’t generate the actual NWScript function names, obviously.
If you really need to do the latter, I suspect ExecuteScriptChunk() will do it. The chunk would need to include the function library. I haven’t tested it, but the example seems to confirm that #include is OK.
Yes, I was trying to be a little more elegant than that but I suppose I could just implement it the ugly way unless someone knows a better one.
The whole point is to just have all the info easily readable in one file, right? It doesn’t make much sense to put script info into the 2DA since you’ll have to edit both the scripts and the 2DA when changing things… Why complicate when you can keep it simple with something like:
const int CMD_EXECUTE = 1;
const int CMD_VALIDATE = 2;
const int CMD_GET_ANIM_LENGTH = 3;
int command(object pc, string name, int act = CMD_EXECUTE) {
switch (HashString(name)) {
case "help":
if (act == CMD_EXECUTE) return cmd_help(pc);
if (act == CMD_VALIDATE) return TRUE;
break;
case "givexp":
if (act == CMD_EXECUTE) return cmd_givexp(pc);
if (act == CMD_VALIDATE) return is_privileged(pc);
break;
case "dance":
if (act == CMD_EXECUTE) return do_emote(pc, EMOTE_DANCE);
if (act == CMD_VALIDATE) return !GetIsInCombat(pc);
if (act == CMD_GET_ANIM_LENGTH) return 3000;
break;
...
}
return 0;
}
adjust form and function to taste.
What I’m trying to do is have the name of the script to call stored in the 2da, but that approach could work too.
You can do that, as Proleric said, by using ExecuteScript(). It’s pretty close to free. And you are saying “name of the script” not “name of the function” (at least in this response) so have at it. You will end up with a lot of tiny scripts (which may not be the issue it used to be - I don’t remember if EE raised the 16k resource limit).
Or a larger script with switch() (or a lot of if/else which end up about the same in the generated code).
There is no function pointer or dynamic linking or similar so it needs to be an actual script name not a function name (unless you name the script the same as the function yadda yadda…)
I was, ideally, hoping to iterate over table lines and have it reference functions. I did see (from heartlib) you can reference scripts, but not individual functions. It looks like we don’t have that kind of granularity, as it were?
Right, invoking a script by name is easy (ExecuteScript()
). You can even have multiple names for the same script (symlinks, nwsync manifest dedup, resman overrides) and then the script can use GetScriptName()
to know under which name it was invoked. But at that point, you might as well just pass a param and dispatch based on that.
What you cannot* have is a way to reference a function by name, other than:
(a) Using the JIT compiler (e.g. ExecuteScriptChunk()
) to compile a new version of the NCS on the fly
(b) Creating dispatch tables ahead of time (manually or auto generated), such as a case
for every possible function.
With CompileScript()
function, the above is actually not too horrible. You can parse your 2DA on module load, and generate one or N script chunks as needed, CompileScript()
them all and give them appropriate names.
I did see (from heartlib) you can reference scripts, but not individual functions.
Since this was a surprise for you, I can recommend reading a bit about how NCS/nwscript VM works, such as here: NWN Data Files - NCS File. And/or, use nwn_asm
from GitHub - niv/neverwinter.nim: CLI tools and nim library used in Neverwinter Nights: Enhanced Edition development to get a feeling of what goes into the NCS and what doesn’t. Trivially, you could run strings(1)
on an NCS and you’ll see no function names are present, because they are not used by execution. They exist in the NDB file only, for symbolized backtraces and stuff, but the VM never touches those.
Right, some reading for me then.
Essentially, what I’m trying to make is a modular parser for chat script commands which allows people to easily drop in new script commands without having to touch the code for the parser, just for their specific command. So they add the new code scripts without having to touch further into the backend.
Right now what I have is a big case statement they’d have to edit, which isn’t the end of the world but I want to make things easier for other people who want to drop stuff into what will hopefully end up being a framework.