Skip to content

Request - more generalised scripting?

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?

Comments

  • highv_priesthighv_priest Member Posts: 50
    Object constructors aren't that simple. For example... Objects in games do not always use the same memory space. The actual get/set is a pointer to where the object is based on it's opening and closing bytes in memory.

    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.
  • SherincallSherincall Member Posts: 387

    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.

    Not really. You can find an actual object (CGameObject/CNWSObject) given its ID (i.e. the nwscript object type). But you can also iterate through all objects and select one based on tag/resref/position/etc.

    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.

    That is immensely difficult to provide in a safe manner. NWNX is powerful, but it is most definitely unsafe - in the sense that it has unsandboxed access to the server machine. This is not a big deal if the person writing the code has that access anyway, which is always the case for servers running it. But, if you had NWNX on the clients - or what you propose above - it wouldn't be hard for singleplayer module authors to distribute a virus with their mod that has access to the rest of the player's machine.

    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 ).
  • highv_priesthighv_priest Member Posts: 50


    Not really. You can find an actual object (CGameObject/CNWSObject) given its ID (i.e. the nwscript object type). But you can also iterate through all objects and select one based on tag/resref/position/etc.

    Utilizing that method is what causes a lot of instabilities of certain nwnx dlls such as nwnx_funcs. This is especially true for player objects which can transition states independently of the control of the server and do not naturally have a tag or resref. Practically every function call from funcs has to have a GetIsObjectValid check before running it or it will instantly crash the server if a player end tasks their client right when it executes(something easily abused with any calls encapsulated in delaycommand). The writer of the NWNX function call can run the object valid check, but my testing on that has not shown good results. You'd think with nwn being single threaded such things couldn't happen, but the actual player client isn't part of the single thread and it can. I prefer having a fallback, but to each their own I suppose.


    That is immensely difficult to provide in a safe manner. NWNX is powerful, but it is most definitely unsafe - in the sense that it has unsandboxed access to the server machine. This is not a big deal if the person writing the code has that access anyway, which is always the case for servers running it. But, if you had NWNX on the clients - or what you propose above - it wouldn't be hard for singleplayer module authors to distribute a virus with their mod that has access to the rest of the player's machine.

    Who said using nwnx? I specifically mentioned modifying the innate SetLocal commands to be able to modify memory space(of which the range of memory space is easily identifiable and enforceable in the base command to prevent modifying memory outside of the actual intended game).

    GetStruct and SetStruct probably works better though.
  • SherincallSherincall Member Posts: 387
    Player end-tasking their client and causing an issue on the server is always a bug. Many such bugs were fixed in EE, if you find any remaining, report them privately to the devs.

    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:

    Who said using nwnx?

    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 specifically mentioned modifying the innate SetLocal commands to be able to modify memory space(of which the range of memory space is easily identifiable and enforceable in the base command to prevent modifying memory outside of the actual intended game).

    I have two objections here:
    1. Extending the existing functions to cover such a different functionality seems like a compatibility/maintenance nightmare. Better add new ones.
    2. "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.
  • SherincallSherincall Member Posts: 387
    Ah, I misunderstood a bit earlier. I'm guessing you already know all this, but I'll clarify for anyone else reading the thread.

    "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.
  • highv_priesthighv_priest Member Posts: 50
    I'm just picking the thing EVERYONE could use instead of the dedicated scripter types.

    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.
  • prwoprwo Member Posts: 69

    Something like SetLocalInt(oCreature, "DEVASTATING_CRITICAL_DC", -1);
    [...]
    For example would fix a lot of problems for A LOT of players.

    I can totally relate to that, only I would make it even more general:

    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.
Sign In or Register to comment.