ZDaemon Forum Index ZDaemon
Client/Server DOOM
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Best way for handling custom console variables in ACS script

 
Post new topic   Reply to topic    ZDaemon Forum Index -> Wad Editing
View previous topic :: View next topic  
Author Message
Mr. Satan
has entered the game!


Joined: 30 Apr 2015

PostPosted: Tue May 19, 2015 4:27 am    Post subject: Best way for handling custom console variables in ACS script Reply with quote

What is the best way to handle custom console variables that are changed using set?
I want to be able to restrict the min and max value that the variable can be set to and display a message telling the person the valid range if they try to set it too high or low. It needs to be a gobal variable.
So far I have something like this.

Code:

int variable1 = z;

script 1 OPEN
{
    if GetCVar("variable1") < x &&
       GetCVar("variable1") > y)
    {
        log(s:"Variable1 needs to betwen x & y.);
    }
    else if GetCVar("variable1") > x &&
              GetCVar("variable1") < y)
    {
        variable1 = GetCVar("variable1"); 
    }
    restart;
}


This works but trying to set the value too or high spams the error message in the console window.
Back to top
View user's profile Send private message
Sr69Mm-jC
Unstoppable!


Joined: 21 Jul 2011

PostPosted: Tue May 19, 2015 8:28 am    Post subject: Reply with quote

The best way to do it is not very trivial. Here's the code that does it:

Code:
#include "zcommon.acs"

int variable = 5;

script 100 OPEN
{
    int variable_upd = 0;
    int variable_min = 1;
    int variable_max = 9;
    int temp;
    while (true)
    {
        temp = GetCVar("variable");
        if (variable_upd != temp)
        {
            variable_upd = temp;
            if (variable_upd >= variable_min && variable_upd <= variable_max)
            {
                variable = variable_upd;
                Log(s:"Accepted new value for 'variable': ",d:variable);               
            }
            else
            {
                Log(s:"Incorrect value for 'variable': ",d:variable_upd,
                    s:" (must be between ",d:variable_min,s:" and ",d:variable_max,s:")");
            }
        }
        Delay(5);
    }
}


This code clamps the "variable" between 1 and 9. Whenever the user writes "set variable x", the code will either tell him their input was accepted or displays an error message which also provides the valid range. The idea is that the code additionally stores the value it got by calling GetCvar(), and only updates the variable (or prints the error message) when the new GetCVar() is different from what is stored.

I assume, however, that you'd want to deal with multiple custom variables - which means each of them has its own valid range, default values etc. In this case you'll need a more general approach to this problem, so I wrote the following code. Please don't mind how lengthy it is; you will only need to copy-paste it. There will be a short nice example on how to use it a bit later.

Code:
//////////////////////////////
// Custom Variables Manager //
//////////////////////////////

// NOTE
// Please copy-paste this entire code to a separate file
// (e.g. "varmanager.acs") and #include that file in your scripts.

// Maximum number of variables that this code supports.
// You can easily extend it if you need to.
#define MAX_VARIABLES 100

int var[MAX_VARIABLES];      // The variable itself
int var_upd[MAX_VARIABLES];  // Temporary value for GetCVar updates
int var_def[MAX_VARIABLES];  // Default value
int var_min[MAX_VARIABLES];  // Minimum value
int var_max[MAX_VARIABLES];  // Maximum value
str var_name[MAX_VARIABLES]; // The name of this variable

// Do NOT remove this line or the whole code will most likely break (it depends..)
str _nullstr = "";

// Adds the new variable.
function bool CreateVariable(str name, int defval, int minval, int maxval)
{
    int vid; // this will be the index of the new variable unless something goes wrong
    for (vid=0; vid<MAX_VARIABLES; vid++)
    {
        if (var_name[vid] == name)
        {
            Log(s:"\ca(CreateVariable) ERROR: Couldn't create variable '",s:name,
                s:"'. Reason: this variable already exists.");
            return false;
        }
        if (var_name[vid] == 0) break;
    }
    if (vid == MAX_VARIABLES)
    {
        Log(s:"\ca(CreateVariable) ERROR: Couldn't create variable '",s:name,
            s:"'. Reason: too many custom variables (already ",d:MAX_VARIABLES,s:" of them).");
        return false;
    }
   
    // Swap minval and maxval if neccessary
    if (minval > maxval) { int temp = minval; minval = maxval; maxval = temp; }
   
    // Ensure that the default value is valid
    if (defval < minval || defval > maxval)
    {
        Log(s:"\ca(CreateVariable) ERROR: Couldn't create variable '",s:name,
            s:"'. Reason: bad default value (",d:defval,s:") (max = ",d:maxval,s:", min = ",d:minval,s:").");
        return false;
    }
   
    // Everything looks good, so let's create it.   
    var_name[vid] = name;
    var_min[vid] = minval;
    var_max[vid] = maxval;   
    var_def[vid] = defval;
    var[vid] = defval;
    return true;
}

// Gets a variable by its name.
// Like GetCVar(), it returns 0 for variables it doesn't recognize.
function int GetVariable(str name)
{
    if (name == 0) return 0;
    for (int vid=0; vid<MAX_VARIABLES; vid++)
    {
        if (var_name[vid] == name) return var[vid];
    }
    return 0;
}

// Sets a variable by its name.
// Does nothing if it cannot recognize the variable name.
function void SetVariable(str name, int value)
{
    if (name == 0) return;
    for (int vid=0; vid<MAX_VARIABLES; vid++)
    {
        if (var_name[vid] == 0) return;
        if (var_name[vid] == name)
        {
            var[vid] = value;
            return;
        }
    }
}

// This script updates variables based on GetCVar input.
script 499 OPEN
{
    int temp;
    while (true)
    {
        Delay(10);
       
        // For every variable...
        for (int vid=0; vid<MAX_VARIABLES && var_name[vid] != 0; vid++)
        {
            // If this variable was changed..
            temp = GetCVar(var_name[vid]);
            if (var_upd[vid] != temp)
            {
                // Store the new value in an intermediate variable.
                var_upd[vid] = temp;
                if (var_upd[vid] == 0)
                {
                    // If the new value is 0, our variable will be set to its default value
                    // (like if it was never changed by the user in first place).
                    // As such, 0 is always valid input regardless of the specified range.
                    var[vid] = var_def[vid];
                    Log(s:"(VarManager) Variable '",s:var_name[vid],s:"' is set to default value (",d:var[vid],s:").");
                }
                else if (var_upd[vid] >= var_min[vid] && var_upd[vid] <= var_max[vid])
                {
                    // The new value is non-zero and is in the valid range.
                    var[vid] = var_upd[vid];
                    Log(s:"(VarManager) Accepted new value for variable '",s:var_name[vid],s:"': ",d:var[vid],s:".");
                }
                else
                {
                    // The new value is non-zero and is not in the valid range.
                    Log(s:"\ca(VarManager) Incorrect value for variable '",s:var_name[vid],s:"': ",d:var_upd[vid],
                        s:" (must be between ",d:var_min[vid],s:" and ",d:var_max[vid],s:").");
                }
            }
        }
    }
}


Simply create a file named "varmanager.acs" and dump this whole code into it. Now, there are two ways you can use it: the bad way and the nice way. First, the bad way:

Code:
#include "zcommon.acs"
#include "varmanager.acs"

// Make sure to assign a high enough script number here.
script 998 OPEN
{
    // CreateVariable("name", defaultvalue, minvalue, maxvalue);
    CreateVariable("monstercount",  10,   1,    50);
    CreateVariable("mysectortag" , 100,   1, 65535);
    CreateVariable("spawncybers" ,   0,   0,     1);
}

script 1 (void)
{
    int monsters = GetVariable("monstercount");
    PrintBold(s:"Currently, the monster count is set to ",d:monsters);
}


This should be self-explanatory. As you can see, you need to 'create' the variables in the first script (providing the name, default value, min value, and max value for each of them). The manager will now automatically update them if the user writes "set monstercount 20" or the such (and the same success/error messages will be Log()'ed). To get the current value of your variable, use the new GetVariable("variable_name") function.

But why is it the bad way, you might ask? What's wrong with it? Well, if you accidentally write, say, GetVariable("montsercount") (see that typo in the variable name?), the code will compile just fine but it won't work the way you expect it to (as "montsercount" will be a totally different variable than "monstercount"). This means you'll have bugs that are hard to figure out and fix. So here's the good way:

Code:
// Actual names of the variables
#define V_MONSTERCOUNT "monstercount"
#define V_MYSECTORTAG  "mysectortag"
#define V_SPAWNCYBERS  "spawncybers"

// Make sure to assign a high enough script number here.
script 998 OPEN
{
    // CreateVariable(V_NAME, defaultvalue, minvalue, maxvalue);
    CreateVariable(V_MONSTERCOUNT,  10,   1,    50);
    CreateVariable(V_MYSECTORTAG , 100,   1, 65535);
    CreateVariable(V_SPAWNCYBERS ,   0,   0,     1);
}

script 1 (void)
{
    int monsters = GetVariable(V_MONSTERCOUNT);
    PrintBold(s:"Currently, the monster count is set to ",d:monsters);
}


Here, we defined all our variable names. We will now use V_MYVAR instead of "var" everywhere else. So if we accidentally write V_MONTSERNAME in our code it will throw a compilation error right away - letting us quickly fix the typo before it even has a chance to cause problems. It looks less pretty than the previous method but this advantage pays off for sure.

Now let's see this code in action:


Initially, "monstercount" is 10 (default value that we specified in our script). Now, normally, if you write GetCVar("some_variable_that_has_never_been_set_by_user") it will return 0. This means that you cannot tell whether the user has explicitly set a variable to 0 or never changed it. So setting that variable to 0 does nothing and our code also does nothing.
Then, we try to set it to values outside the range we specified in our script (1000 and -1000). The code detects this and ignores these values. Finally, we set it to 30 (which is in the valid range), and in script 1, GetVariable("monstercount") indeed returns 30.
We then write "set monstercount 0". This again makes it look as if the user has never changed it, so we also set our variable to its default value of 10 (as if it has never been changed). Note that 0 is outside the range we specified; as such, it is treated as a special value regardless of the range.
In the end we try to "set montsercount 30" (there's the typo) and nothing happens. It is because we never Created such variable for our manager.

Note 1: you cannot set such variables in the code at all. By design, such custom variables can be changed by the user at any time, so even if we were able to set one, it could be overwritten by the user almost instantly. Or maybe it wouldn't be. We can't even tell, so no point in being able to set it.
Note 2: you might want to replace all Log() calls with Print() calls in my lengthy code if you intend to use it online. You could even comment them out altogether, it's up to you.
Note 3: once the map is loaded, there is a tiny delay before my manager starts checking GetCVar(). As such, if some of your OPEN-type scripts rely on variables that already exist on map load (e.g. the ones that were set in the server's config file), make sure you wait at least a few tics before you do your initialization.
Note 4: do not use my manager for existing variables, such as "dmflags" - simply GetCvar() those. Only use it for new variables that you want to create.

I'm glad this problem was brought to my attention, and I hope other modders can also find this useful.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    ZDaemon Forum Index -> Wad Editing All times are GMT + 1 Hour
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group