Request - more generalised scripting?
Proleric
Member Posts: 1,316
Currently, the major constraint in nwscript is that we can only get and set those object properties which have specific functions (e.g. GetTag, SetPlotFlag).
True, we have the very powerful Get2daString, which allows every field in any 2da to be read by name.
We also have moneo, which allows builders to script changes to any field in almost every game file prior to play.
It would be very cool if, in game, there was a general-purpose script command to get and set any and all object properties by name.
Likewise, the ability to access the elements of structures like event and itemproperty by name (just as we can set vector.x etc).
If this were possible, it might be a better use of Beamdog dev time than adding new functions piecemeal.
Or is this just pie in the sky?
True, we have the very powerful Get2daString, which allows every field in any 2da to be read by name.
We also have moneo, which allows builders to script changes to any field in almost every game file prior to play.
It would be very cool if, in game, there was a general-purpose script command to get and set any and all object properties by name.
Likewise, the ability to access the elements of structures like event and itemproperty by name (just as we can set vector.x etc).
If this were possible, it might be a better use of Beamdog dev time than adding new functions piecemeal.
Or is this just pie in the sky?
5
Comments
The reason nwnx is capable of modifying objects is that it piggybacks off of the ExecuteScript function call. Otherwise the actual pointers to the objects are completely unknown.
I think a better use of their time would be to give us a function which can take an object and break it into it's data types and memory addresses and allow us to use setlocal on those memory addresses instead of an object.
It's a lazy solution, but it would work universally.
What could be done is to serialize the game object to GFF (like StoreCampaignObject does), then edit that GFF and deserialize. But that API would be a nightmare to work with.
I think Proleric's request is closer to what is feasible for singleplayer - A generic GetStructXxx/SetStructXxx that takes a field name as string, and checks it against a whitelist of available fields. That way, adding new fields as requested by the community would be a 1-minute task for BD (just expanding the whitelist with ).
GetStruct and SetStruct probably works better though.
NWNX has a bit of a higher threshold for what counts as a bug, but I'd consider those things you reported bugs as well. Old NWNX is no longer supported, but if you end up using the new one and see something like that, report it and I'll fix it.
Point I'm trying to make is, let's not base feature requests to work around bugs. Bugs should always be fixed.
Anyway, back to the topic: I just mentioned it as a counter-example. I.e. "this thing we sometimes do in nwnx should never ever be done in the actual game". I have two objections here:
- Extending the existing functions to cover such a different functionality seems like a compatibility/maintenance nightmare. Better add new ones.
- "easily identifiable and enforceable" isn't so easy. There are many cases where a pointer is found in the middle of otherwise exposeable variables. That pointer must not be modified, but some of the memory it points to should be, while other can have more pointers. If you want an example, look at CNWSCreature and CNWSCreatureStats. Same for any variable that is used as an index into an array - it needs to be sanity checked so it doesn't access out of bounds.
I don't know which interface would work best for scripters (which is what the trello card discussions are about), but I'm pretty certain the "correct" implementation would be some sort of a whitelist for the fields, instead of trying to enforce memory bounds.The best interface really would just be the option to override all the standard data in the game using local variables. Most of the problems with removing hardcoding for the various feats and such is that you need to actually replicate code from one programming language to another and often times the code doesn't work exactly the same way. Requiring you to proofread and double check that it works right(a nightmare I experienced updating my company from it's fucking old 2003 software).
However changing a local variable is quick and dynamic and can be called within the innate programs language with little work from all parties. Hell the game even conveniently lets you put local variables on things manually in toolset making it a friendly option for the programming illiterate too.
Something like SetLocalInt(oCreature, "DEVASTATING_CRITICAL_DC", -1);
SetLocalInt(oCreature, "CRITICAL_THREAT", 12);//Out of 20
SetLocalInt(oCreature, "CRITICAL_MULTIPLIER", 3);
For example would fix a lot of problems for A LOT of players. Look at that I just modded the devastating critical feat to give a 12-20 crit range and x3 multiplier.
If they just had a list of all the names of the variables(which is easier to do than making a whitelist to a possibly gigantic struct) and went through and added an if check for these locals that would fix the game very quickly with very little actual work involved. One of them could literally softcode the entire game's feat variables in a single 8 hour work day, with both 15 minute breaks and a 30 minute lunch.
"Local variables" and actual game object variables exist in totally different namespaces. In pseudoc++, that example looks something like this:
class Creature { // Creature member variables int nCriticalThreat; int nCriticalMultiplier; ... struct LocalVariable { string sVarName; enum { INT, FLOAT, STRING, ... } eType; union { int n; float f; string s; ...} value; } List<LocalVariable> lstLocalVars; };
(For actual code, see here)When you call SetLocalInt(), it will create a new LocalVariable, and add it to that list. It does not understand actual members at all.
What @highv_priest is proposing is special handling for cases when the variable name is "nCriticalThreat" (or some 1:1 mapping of it), instead of going into the list and creating a real local variable, it should instead refer to the actual class member variable.
Assuming the special variable name is prefixed with something like "NWN:EE INTERNAL" so it doesn't collide with any existing variables anywhere, this will work for most cases. I see some potential issues with deeply nested structures, arrays/lists and the like, but that can be ironed out.
I would still advise having a separate functions for separate functionality, but that's a matter for builders to decide what they prefer.
Having dedicated functions for modifying things such as abilities past +12 and changing internal properties of item properties and effects is wonderful, but what possible dedicated functions would exist for softcoding core game mechanics? ResolveSneakAddDamage? DevastatingCriticalDisableModifyWith? Or worse they'd need to add functions for everything and events for everything.
There should be like a SetPropertyInt/String/Object / GetPropertyInt/String/Object call with which you can address and modify a given object's private member variables - all of them. E.g.:
SetProperty(oNPC, "Conversation", "someresref"); SetProperty(oDoor, "OnUnlock", "someresrefscript");
Think of it as a general interface to access game internals using defined String attributes ("Conversation", "OnUnlock"). Instead of having a getter/setter for each and every attribute of every object defined in the scripting language itself the programming effort to accomplish this goal is reduced to basically implementing a mapping table.
The implementation of SetProperty/GetProperty would be rather simple methinks, because all elements to do so are already existing the NWN scripting language today. Internally I imagine it would look something like this:
int SetProperty (object, key, value){ if (key == "Conversation") object.Conversation = value; if (key == "OnUnlock") object.onUnlock_event = value; // and so forth }
It's not beautiful, it's not elegant, but it would get the job done in no time.