Zum Inhalt wechseln

Foto

Emulating Deprecated_ExecuteScript() or fixing it in script.ldf


  • Bitte melde dich an um zu Antworten
4 Antworten in diesem Thema

#1
DarthGizka

DarthGizka
  • Members
  • 867 Beiträge

The version of script.ldf that is contained in patch.003 contains an erroneous declaration for Deprecated_ExecuteScript(), which makes it take the script name as a resource instead of as a string. When the engine pops the script name and finds a string tagged as 'resource' instead of as 'string', it panics and quietly aborts execution of the guilty script.

 

One way around this is to use HandleEvent() or HandleEvent_String() instead, e.g. by passing Event(0) as the first parameter. However, that doesn't work if the script must be run on a certain object, to which it refers using OBJECT_SELF. In that case you need a fixed declaration for ExecuteScript() for the benefit of the toolset's compiler.

 

Extract script.ldf from patch.003 and add the following line:

void Corrected_ExecuteScript( string rScript, object oTarget ) = 8;

 

Then put the file into an override directory.

 

Here's a little test script that I used to figure out what's what. Replace my "aa_dump_args" guinea pig with something of yours that's suitable, something that has a visible effect (e.g. by printing floaties or whatever).

 

// aa_ExecuteScript.nss
// 2014-07-31

#include "core_h"

// The extension for the resource doesn't matter as it is stripped internally, but the compiler
// wants something with no more than three characters after the dot. By contrast, script name
// *strings* must not contain an extension.

const resource SCRIPT_RES_ = R"aa_dump_args.foo";
const string   SCRIPT_STR_ =  "aa_dump_args";

void main ()
{
   string[] argv = SplitString(GetLocalString(GetModule(), "RUNSCRIPT_VAR"));
   string cmd = argv[0];

   SetLocalString(GetModule(), "RUNSCRIPT_VAR", "parameters...");

   if (cmd == "emulated")
   {
      // HandleEvent() needs a properly constructed event; a blank stack variable won't do.
      // Note: IsValidEvent() is broken and returns FALSE for events of type 0; there seems
      // to be no way of distinguishing between an invalid event and one of type 0, apart
      // from passing it to a function like HandleEvent() to see what happens.

      HandleEvent(Event(0), SCRIPT_RES_);
   }
   else if (cmd == "string")
   {
      HandleEvent_String(Event(0), SCRIPT_STR_);
   }
   else if (cmd == "string_with_ext")
   {
      // specifying an extension b0rkens things...

      HandleEvent_String(Event(0), SCRIPT_STR_ + ".nss");
      HandleEvent_String(Event(0), SCRIPT_STR_ + ".ncs");
   }
   else if (cmd == "corrected")
   {
      // properly declared version of function #8 (ExecuteScript)

      Corrected_ExecuteScript(SCRIPT_STR_, GetMainControlled());
   }
   else if (cmd == "corrected_with_ext")
   {
      // specifying an extension b0rkens things...

      Corrected_ExecuteScript(SCRIPT_STR_ + ".nss", GetMainControlled());
      Corrected_ExecuteScript(SCRIPT_STR_ + ".ncs", GetMainControlled());
   }
   else if (cmd == "original" || cmd == "")
   {
      // The declaration of Deprecated_ExecuteScript() in script.ldf is broken. The function expects
      // the script name as a plain string, not as a string that is tagged as 'resource'. Since it
      // finds the latter instead of the former, it aborts the script quietly as is usual for this
      // sorry mess, and the RUNSCRIPT command misleadingly prints "Success".

      Deprecated_ExecuteScript(SCRIPT_RES_, GetMainControlled());
   }
   else
   {
      DisplayFloatyMessage(GetMainControlled(), "unknown cmd: '" + cmd + "'", FLOATY_MESSAGE, 0xFF0000);
   }

   DisplayFloatyMessage(GetMainControlled(), "reached the end of the script", FLOATY_MESSAGE, 0xFFFF);
}


#2
Sunjammer

Sunjammer
  • Members
  • 925 Beiträge

You might get what you need through using DEBUG_ConsoleCommand("runscript nameOfYourScript withAnyParams youLike"); That may avoid both the need to hack script.ldf and using a deprecated function (that comes with a Georg Zoeller health warning no less (although it was probably aimed at BioWare designers)).

 

Either way I would suggest using the version of script.ldf found in patch002.erf. The version in patch003.erf is an older version that was included in v1.04 patch by mistake and it's missing a couple of functions. Unfortunately the issue wasn't fixed in v1.05.

 

Regarding your comments about invalid events it is worth noting the literal value of the EVENT_TYPE_INVALID constant is 0 so I suspect IsValidEvent is working as designed. I haven't tested it but I also suspect that simply declaring an event may create an event with an event type of 0.



#3
DarthGizka

DarthGizka
  • Members
  • 867 Beiträge

You might get what you need through using DEBUG_ConsoleCommand("runscript nameOfYourScript withAnyParams youLike"); That may avoid both the need to hack script.ldf and using a deprecated function (that comes with a Georg Zoeller health warning no less (although it was probably aimed at BioWare designers)).


I re-ran all tests with DEBUG_ConsoleCommand(), and there was no change compared to ExecuteScript() or HandleEvent(). Nor was any to be expected: the call is synchronous, meaning the script engine cannot simply discard the current execution context and start with a clean slate as is the case with script invocation by asynchronous events.

Regarding the warning: Georg Zoeller is probably the only one who is able to gauge when it is safe to use the function and when not. Putting the function out there for all to see with a cutesy prefix accomplishes little... He should have fixed the function or removed its declaration. Or at the very least given the pertinent information.

On the whole I agree that deprecated functions, hacked system headers and things like that should not be used unless strictly necessary, as a matter of last resort. However, with things like DA Script - where the interface is neither minimal nor complete - we often have to make do with what's available... And things are much more relaxed for scripts that run only on one's own box, i.e. for spelunking, research and testing.
 

Either way I would suggest using the version of script.ldf found in patch002.erf. The version in patch003.erf is an older version that was included in v1.04 patch by mistake and it's missing a couple of functions. Unfortunately the issue wasn't fixed in v1.05.

 
You are spot on - I confused patch v1.03 (which contains patch002.erf) and patch003.erf which is part of patch v1.04 and contains the same script.ldf as patch001.erf. The ldf I've been using - because of of GetAbilityList() and RequestTarget() - is the v1.03 edition. It doesn't help that the ldf does not contain any identifying marks like last modification date, revision id or whatever...

In order to atone for my blooper I used a bit of SQL fu to find all function declarations that are new or changed in v1.03:

object GetCommandObject(command cCommand, int nIndex = 0) = 550;
command SetCommandObject(command cCommand, object nCommandObject, int nIndex = 0) = 551;
command CommandFly(location lLocation, int bIgnorePathing = FALSE) = 811;
int GetConversationEntryParameter( ) = 443;
int IsPackageLoaded( string sPackageUID ) = 862;
int[] GetAbilityList( object oCreature, int nType = ABILITY_INVALID, int bOnlyCoolingDown = FALSE ) = 859;
void RequestTarget(int nTargetType, float fAOEParamater1, float fAOEParamater2, int eventID, object oObject, string scriptname = "") = 860;
void SetPlotActionCooldown(int nPlotActionId, float fCooldownTime, int nEventID, object oObject, string scriptname = "") = 861;

 
The following function declarations are not commented out but do not have an implementation, meaning that calling them will abort script execution quietly:

itemproperty GetLocalItemProperty( object oObject, string sVarName ) = 93;
void SetLocalItemProperty( object oObject, string sVarName, itemproperty iItemProperty ) = 94;
void SetMapPatchState( object oMapPatch, int nState) = 649;
int GetMapPatchState( object oMapPatch ) = 650;


The following functions are commented out but still seem to have non-trivial implementations (other than popping and destroying passed arguments):

//void AssignFunction( object oTarget, function fnFunction ) = 6;
//void DelayFunction( float fSeconds, function fnFunction ) = 7;
//void AddToParty( object oCreatureToAdd, object oPlayer ) = 396;
//void RemoveFromParty( object oCreature ) =  397;
//int GetPartySize( object oPlayer ) = 398;
//command CommandDoFunction( function fFunction ) = 585;
//command CommandDoEvent( event evToQueue ) = 721;


Quite a few other functions, like PrintToLogAndFlush() are not commented out but are effectively no-op. However, they are difficult to identify because they throw lots of code at the job of doing nothing at all and cannot be weeded out by simple scripts. Basically, they have to be investigated and analysed one by one when they fall under suspicion.
 

Regarding your comments about invalid events it is worth noting the literal value of the EVENT_TYPE_INVALID constant is 0 so I suspect IsValidEvent is working as designed. I haven't tested it but I also suspect that simply declaring an event may create an event with an event type of 0.


Simply declaring an event effectively creates a null pointer. Some functions are safe to use with such an invalid event, and they simply return default values. However, an invalid event is not the same as Event(0), even though GetEventType() returns 0 in both cases. To see the difference, try something like

void main ()
{
   event e;
   object o = GetEventTarget(e);
}

That should serve to illustrate the necessity of testing for invalid events. By contrast, Event(0) is a valid event that is safe and fully functional in every regard, and it makes a good data carrier because it cannot possibly clash with any event meant as such. It is also the most logical candidate for the moral equivalent of WM_NULL - something that is valid, well-defined and known to be no-op. Which is not at all the same conceptually as an invalid event, as the little experiment above should have demonstrated.

One way of testing the validity of events would be "IsEventValid(e) || GetEventType(SetEventType(e, 1))", since SetEventType() is no-op for invalid events. Or indeed "GetEventType(e) || GetEventType(SetEventType(e, 1))", with the first part of the term often already computed or implied by a switch case.



#4
Sunjammer

Sunjammer
  • Members
  • 925 Beiträge

I thought you were looking for an alternative for Deprecated_ExecuteScript: I guess I misunderstood the purpose of the thread. If you are looking for ways round the TMI limit then I think SignalEvent and/or DelayEvent are you best candidates.

 

Possibly splitting hairs but Get/SetCommandObject were added by v1.02. However nice information the functions without implementation: I've been meaning to rewrite the itemproperty page on the wiki for ages (since it is an artefact left over from NWN and DA item properties don't use that structure) and this is a timely reminder.

 

EDIT: Added the functions in red to the Broken functions category. I'll fix their pages in due course.



#5
DarthGizka

DarthGizka
  • Members
  • 867 Beiträge

The TMI thing was at the back of my mind, and so I adapted my test scripts to include DEBUG_ConsoleCommand() as a matter of course. However, the engine limits are peripheral to the ExecuteScript() question, especially as only asynchronous execution methods can offer a clean way around the limits.

 

I posted my article primarily because the declaration of ExecuteScript() has been broken from day one and never been fixed, and the workarounds - like HandleEvent_String(Event(0), "scriptname") - are neither obvious nor documented. Nor is the fact that the string-taking executors crap out if the script name contains an extension. I added the test script because it is much more precise than my prose and may answer questions I forgot to address. Hopefully, if someone wants to execute a script from a script they now have better information about the various methods available.

 

DEBUG_ConsoleCommand() can be very handy because the other script execution methods require splitting the command line and farting around with RUNSCRIPT_VAR, or to use events as parameter packets. ExecuteScript() can still be useful because it allows to invoke a script on a specific object without overriding that object's event script (which could not be restored because there is no GetEventScript() function).

 

Yes, I forgot to say that I diffed the whole way from the shipped version of script.ldf to the v1.03 edition, without specifying which change occurred when.

 

My motivation for posting is this: the lack of information and documentation can make scripting DA very difficult and laborious, with lots of googling and experimentation and the scripts aborting quietly at the slightest provocation and no way to peek inside to see what's happening. Printing to the log is a poor substitute, especially as it is not possible to compile the log statements away by redefining a macro. Printing to floaty is very unreliable. So, scripting can be a very uphill battle and having reliable info can make that easier.