Posted: Fri Jan 03, 2014 8:52 pm Post subject: Improved Random for Open and Enter Scripts
Random on Enter Scripts starts always with the same number. Every time you restart e.g. map01 you get the same number. Same happens with other maps, they all have their own number.
Something similar happens with Enter Scripts, but for the first player, only.
It happens Single Player and on servers.
I know this topic is rather old but I'd like to point out of a few things - especially for those who stumble upon this topic while trying to figure out how randomness in Doom works.
1. The problem is not about OPEN and ENTER scripts.
The problem is actually the fact that the RNG seed remains the same each time you initially start the game up. It is hardcoded and it does not depend on any variables that may be different from one game start to another - such as server time, player movement/angles/actions, etc.
2. OPEN scripts are executed on map init.
They are called before anything else manages to call P_Random function (which is the RNG in doom code used by monsters, damage calculations, flickering lights, ACS Random, and other things). No matter how things-that-use-P_Random you have on your map, OPEN scripts will use P_Random before all of those things do.
You see, if you put a long enough delay in an OPEN script before actually using Random() ACS function, then maybe some players could spawn and alert/fight monsters. Since player positions and actions are nondeterministic (that is, they are different every time - unless a demo is played), monsters would also react differently, the damage calculations would be different and thus P_Random would be called different number of times, letting you get actually random values through Random() function in an OPEN script. But this isn't a reliable solution, if it is a solution at all.
3. ENTER scripts are not really a part of the problem.
I believe players can never instantly spawn in the game; there is always at least a few tics of delay between connecting (or starting the map in singleplayer) and entering the game. If you have a simple test map with nothing but a player start then yes, Random() will return the same values (because, again, nothing else is calling P_Random). However, if you have some monsters on the map, then the results will mostly be random - because the number of tics elapsed between connecting and entering can be different, and in the meanwhile the idle monsters could be using P_Random. If it's a multiplayer server then some other players have very likely already caused many P_Random calls. You could even have an OPEN script that uses Random() each tic and that'd be pretty much sufficient. But yes, this is not really a nice solution either.
4. Using GetCVar("cv_gmtime") as a seed is a very bright idea, if not the best one.
Server time is pretty much always different from one game start to another. Using time as a seed for RNG is a very common solution in programming (as long as you simply need unique seeds and you're not concerned about seed security) and it's no different for Doom. Use it.
Slightly unrelated but needed to understand the final section:
5. OPEN scripts are always executed in descending order.
For example, if you have "script 1 OPEN", "script 999 OPEN" and "script 500 OPEN" (defined in this order), then script 999 will be executed first, then script 500, then script 1. Only script numbers matter; it does not matter which script goes first/last/etc in the source code. The same actually applies to ENTER/RESPAWN scripts (if you have multiple of those). I'm not sure about normal (void) scripts.
Finally,
6. Krawa's Randomizer() function is very good but I wouldn't recommend calling it more than once.
It is perfectly sufficient to only use it once, no need to run the loop and read cv_gmtime every time a player enters the game etc. However, if you only call it once you must ensure that it is called before any other OPEN scripts are executed. Hence it should be run in an OPEN script with a script number higher than any other one in your wad. You could simply use script 999 to achieve that.
7. So, here's the best and clearest solution I can think of.
Simply write this anywhere into your ACS code:
Code:
script 999 OPEN
{
int rand = GetCVar("cv_gmtime") % 256;
for (int i = 0; i < rand; i++) Random(0,255);
}
P.S. Minor note: "<=" has been replaced with "<". If rand==255 then it doesn't make sense to call P_Random 256 times in a row as that will have no effect.
20. Implementation of 4 new ACS functions for simpler/better
pseudorandom numbers:
1. int ZD_rand(int min_value, int max_value);
Draws a uniformly distributed random number in the specified
range (the range includes the min. and max. values). It can
work in "repeatable" mode (via the functions below), but
that's NOT the default operation; it defaults to "always
new" drawings.
2. void ZD_srand(int seed);
Sets the seed of the random number generator; if the specified
value is negative, then it will use something "random" based
on the current time and other parameters. This function gets
auto-called with seed = -1 on every map load, which results
in "always new" drawings; if you want repeatability, you can
call the function with a fixed (and non-negative) seed value
(eg., 0).
3. void ZD_rand_savestate();
Saves the current state so you can restore it later.
4. void ZD_rand_restorestate();
Restores a previously saved state so you can re-draw the same
random numbers.
These extensions require a new "zdaemon.acs" file for ACC (the
ACS compiler); you can download it from:
http://downloads.zdaemon.org/zdaemon.acs
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