Skip to content

waitFor

MicroBlaster edited this page Oct 2, 2019 · 4 revisions

Purpose: Pauses script execution, waiting for a certain string of text from the remote server before continuing.

Syntax: waitFor {value}

{value}: The value to wait for.

Notes: If a trigger activates while this command is active, the command is immediately forgotten.

There is no reason why this command should be used instead of a trigger, except for the fact that it is easier to manage.

--- Micro --- I think you might not get what you are expecting if you use this command, in particular, the CURRENT_LINE system variable will not contain the line that triggered the command. I recommend using setTextTrigger or Script Macros WaitOn instead. The macro literally creates a text trigger. Please read Shadows Comments blow for further information.

Example:

waitfor "(ENTER for none):"
send LoginName "*"
waitfor "Trade Wars 2002 Game Server"
send Game
waitfor "module now loading."
send "*t***" Password "*"

Notes from Shadow:

The use of "WAITFOR" versus "WAITON"

One is faster! No, it's the other! Never ever use "waitfor", it's bad!

I've heard all of this from various people over the years, so when I started digging into the released TWX delphi code, this was one of the first things I researched. What I found surprised me, and was confirmed once I had a working decompiler.

The following code:

waiton "waiton_test"
waitfor "waitfor_test"

Decompiles to this (after being compiled):

settexttrigger WAITON1 :WAITON1 "waiton_test"
pause
:WAITON1
waitfor "waitfor_test"

You're probably thinking "huh, what?" Yes -- "waiton" is simply a hard coded macro for a trigger. And it's actually worse than that -- it always calls the triggers WAITON1, WAITON2, etc, within a given namespace, so there can potentially be problems with things like merging scripts together using includes.

Let's look at how the code compiles.

(a) waiton "waiton_test"

Code:
<S0> <L1> ---START COMMAND---
<S0> <L1> COMMAND: SETTEXTTRIGGER
<S0> <L1> PARAM_CONST: "WAITON1"
<S0> <L1> PARAM_CONST: :WAITON1
<S0> <L1> PARAM_CONST: "waiton_test"
<S0> <L1> ---END COMMAND---
<S0> <L1> ---START COMMAND---
<S0> <L1> COMMAND: PAUSE
<S0> <L1> ---BEGIN END LABELS---
<S0> <L1> LABEL FOUND: WAITON1
<S0> <L1> LABEL PARSED: WAITON1
<S0> <L1> ---FINISH END LABELS---
<S0> <L1> ---END COMMAND---

(b) waitfor "waitfor_test"

Code:
<S0> <L2> ---START COMMAND---
<S0> <L2> COMMAND: WAITFOR
<S0> <L2> PARAM_CONST: "waitfor_test"
<S0> <L2> ---FINISH END LABELS---
<S0> <L2> ---END COMMAND---

Okay, so the waitfor definitely compiles smaller and takes less cycles to execute, but what functions does it call within the code, is there a difference there? So, let's take a look.

(a) waiton "waiton_test"

First, let's see how the triggers are defined/instantiated:

Code:
public static ScriptRef.TCmdAction CmdSetTextTrigger(Object Script, ScriptRef.TCmdParam[] __Params)
{
    ScriptRef.TCmdAction result;
    string Value;
    // CMD: setTextTrigger <name> <label> [<value>]
    if ((__Params.Length < 3))
    {
   Value = "";
    }
    else
    {
   Value = __Params[2].Value;
    }
    ((TScript)(Script)).SetTextTrigger(__Params[0].Value, __Params[1].Value, Value);
    result = ScriptRef.TCmdAction.caNone;
    return result;
}

Okay, SetTextTrigger is the function that is called for each, which looks like this:

Code:
public void SetTextTrigger(string Name, string LabelName, string Value)
{
    FTriggers[ttText].Add(CreateTrigger(Name, LabelName, Value));
}

And ultimately those triggers are checked in TextOutEvent (part of the "process out" stage of text handling):

Code:
public bool TextOutEvent(string Text, ref bool Handled)
{
    bool result;
    FOutText = Text;
    // check through textOut triggers for matches with text
    result = CheckTriggers(FTriggers[ttTextOut], Text, true, false, ref Handled);
    return result;
}

public static ScriptRef.TCmdAction CmdProcessOut(Object Script, ScriptRef.TCmdParam[] __Params)
{
    ScriptRef.TCmdAction result;
    // CMD: processOut <text>
    if (!(GlobalUnit.TWXInterpreter.TextOutEvent(__Params[0].Value, ((TScript)(Script)))))
    {
   GlobalUnit.TWXClient.Send(__Params[0].Value);
    }
    result = ScriptRef.TCmdAction.caNone;
    return result;
}

Okay, so triggers (all of them) get handled by PROCESSOUT. Which is interesting.

(b) waitfor "waitfor_test"

Here we define the waitfor in this function:

Code:
public static ScriptRef.TCmdAction CmdWaitFor(Object Script, ScriptRef.TCmdParam[] __Params)
{
    ScriptRef.TCmdAction result;
    // CMD: waitFor <value>
    ((TScript)(Script)).WaitText = __Params[0].Value;
    ((TScript)(Script)).WaitForActive = true;
    result = ScriptRef.TCmdAction.caPause;
    return result;
}

This gets scanned in the TextEvent function, which also processes triggers, but notably after the FWaitForActive value:

Code:
public bool TextEvent(string Text, bool ForceTrigger)
{
    bool result;
    bool Handled;
    // check waitfor
    if (FWaitForActive)
    {
   if ((Text.IndexOf(FWaitText) > 0))
   {
       FTriggersActive = false;
       FWaitForActive = false;
       result = Execute();
       return result;
   }
    }
    // check through textTriggers for matches with Text
    result = CheckTriggers(FTriggers[ttText], Text, false, ForceTrigger, ref Handled);
    return result;
}

public static ScriptRef.TCmdAction CmdProcessIn(Object Script, ScriptRef.TCmdParam[] __Params)
{
    ScriptRef.TCmdAction result;
    // CMD: processIn processType <text>
    if ((__Params[0].Value == '1'))
    {
   // process globally for all scripts
   ((TScript)(Script)).Controller.TextEvent(__Params[1].Value, true);
   ((TScript)(Script)).Controller.TextLineEvent(__Params[1].Value, true);
    }
    else
    {
   // process locally only
   ((TScript)(Script)).TextEvent(__Params[1].Value, true);
   ((TScript)(Script)).TextLineEvent(__Params[1].Value, true);
    }
    result = ScriptRef.TCmdAction.caNone;
    return result;
}

So, my thoughts on this topic:

a) It does seem that waitfor() is a simpler function that requires generally less processing cycles and does not have the risk of being prioritized against or bumped by other triggers.

b) Notably, waitfor() is ONLY processed on the PROCESSIN side, while waiton() [as a trigger] is processed at both PROCESSIN and PROCESSOUT. There are definitely implications here, though I am not sure what all of them are -- text should not be changing in a meaningful way between the two but could something get dropped by waitfor() that might be seen by waiton()? Or is waitfor() going to always be faster because it uses less cycles to process and it hits immediately when text comes in (before waiton)?

Clone this wiki locally