News:

One Minute Game Review by The Happy Friar: https://ugetube.com/@OneMinteGameReviews
Also on Rumble: https://rumble.com/c/c-1115371

idTech 4 (aka Doom 3 tech) Discord Server! https://discord.gg/9wtCGHa

Main Menu

Random non-encounters?

Started by calan, October 29, 2014, 08:51:26 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

solarsplace

Hi

Sorry, small bug :(

Change this:

rndNumber = random( 1.0 );

To this:

rndNumber = sys.random( 1.0 );

Cheers

calan

#16
Still no worky.

I've discovered that if I use a spawn_control value of .5 or lower, Bernie never spawns. If I use .6 or higher, he always spawns.

Something else that is a bit odd....if Bernie is set to not spawn (either by a value of 0 or anything below .5), there is a brief flicker of his flame that fades out as the room comes into view. Not sure if that is relevant or not.

EDIT: Would re-seeding the random generator help? How (can) you do that in the D3 scripting engine?
Old bastard but kid at heart...

Ivan_the_B

The seed is only random in multiplayer games.
I guess it made testing the singleplayer maps easier.
You need the SDK to change that.

calan

#18
Ok, I have some more info, and I'm even more confused.  :)


I changed the script up a little. The value for "spawn_control" can now be:

0 = monster never spawns
1 = monster always spawns
2 = monster spawns then dies
3 = one of the above behaviors is randomly selected every time the monster is created.

Here is the script in ai_monster_base.script. Notice that I added some sys warnings to show what is going on.:


/*
=====================
monster_base::monster_begin
=====================
*/
void monster_base::monster_begin() {

float spawnControl = 0;
float rndNumber = 0;

spawnControl = getFloatKey( "spawn_control" );
rndNumber = sys.random(3) ;

if (spawnControl <= 0 ) {
// No chance of spawning, just remove.
self.remove();
sys.warning( "spawnControl = 0; monster not spawned");
return;
}

if (spawnControl > 0 && spawnControl <= 1) {
// always spawn
sys.warning( "spawnControl = 1; monster spawned");
return;
}

if (spawnControl > 1 && spawnControl <= 2) {
// spawn, then kill immediately
self.hide();
self.kill();
sys.warning( "spawnControl = 2; monster spawned and killed");
return;
}

if (spawnControl > 2) {
// randomized.appearances

if (rndNumber > 0 && rndNumber < 1 ) {
// don't spawn
self.remove();
sys.warning( "spawnControl = 3; monster not spawned (random: " + rndNumber + ")");
return;
}
if (rndNumber >= 1 && rndNumber < 2 ) {
// spawn
sys.warning( "spawnControl = 3; monster spawned (random: " + rndNumber + ")");
return;
}
if (rndNumber >= 2) {
// spawn, then kill immediately
self.hide();
self.kill();
sys.warning( "spawnControl = 3; monster spawned and killed (random: " + rndNumber + ")");
return;
}
}

        ...
        ...
        ...



and here is the default value in monster_default.def:


        ...
        ...
"spawn_control"  "3"   //   set to random behavior for testing an unmodified map
"editor_var spawn_control"    "Controls how often monster spawns. 0 = never, 1 = always, 2 = spawn and die, 3 = random. A value of 2 causes the monster to spawn and then be killed immediately, to trigger a random encounter."
        ...
        ...


First, the good news. I loaded up an unmodified Alpha Labs 1 map, and the script seemed to work fine. Unfortunately, their weren't many monsters hanging around. For some reason, the random values assigned to each monster increase in an almost linear fashion as each monster is created. So the first monsters were all between 0 and 1, and never spawned. The middle monsters were set from 1 to 2 and did spawn, and by the end of the map the monsters were in the 2's and were being set to spawn and then be killed. While this may have made for a crazy map (easy at first, nightmare at the end), it isn't what I want.

Why would the random numbers increase as the monsters are being created? (It almost has to be a function of the RNG not being reseeded on each call I think).  How do I get a truly random number between 0 and 3 when each monster is created, if I can't re-seed the RNG each time?

Another issue I'm having is that the spawn_control values in my test map (with 3 monsters) don't appear to be getting passed through. The monsters always get created with whatever spawn_control value I set in the monster_default.def file...or so it seems.

So close!   :)


FWIW, even with this only partially working, playing through the first part of Alpha Labs (which I've gone through a hundred times) was quite different and fun.
Old bastard but kid at heart...

The Happy Friar

I took a look at the random monster/zombie spawner I made years ago and I must of had the same issue with the float numbers.  This is the code I made to randomly choose a zombie and it always worked:
    float num_zom_types = 22 - 1;        //number of zombie types to choose from.  ALL must be setup below!  NOTE: 1 is subtracted because the random function could pick 0
    float x = 0;                //temp counting var
    float random_x = int(sys.random(num_zom_types));            //random number generated
    entity zombie_spawn;            //entity used for spawning


Notice I had 22 possible choices and the random could only be an int.  It would randomly choose between 0 and 21.

EDIT: script is here: http://sterlingshield.net/home/steve/doom3/06jan.html

calan

I'll give the integer approach a try.

Those script utilities look very interesting, if I can ever figure out what they are doing and how to use them.  My approach to using the scripted death of existing monsters to create new ones via sikk's random encounters isn't working out so well (random number issues aside). I usually just end up with a lot less monsters. I really wish it was simpler to just spawn monsters randomly anywhere in a map.

Thanks for the help!
Old bastard but kid at heart...

solarsplace

Quote from: Ivan_the_B on November 05, 2014, 02:45:31 PM
The seed is only random in multiplayer games.
I guess it made testing the singleplayer maps easier.
You need the SDK to change that.

Hi

Well, learnt something new!

Ivan has given us the answer already  ;)

You can cast the float to an int all you like but the outcome will still be the same :P

Because the script calls sys.random at the same time every time on map load, rather than at a random time during the map:

Your random number will always be the same on initial map load if your map scripts events process at a consistent initial game frame each time you load the map. Which in most cases it will unless you change something about how the script timing operates.

This is because if the game type is single player the random number generator is initialized with a random seed value of 0 every time. As Ivan already pointed out, this is likely to make testing single player maps consistent when reloading multiple times maybe...

If it is a multiplayer type game it is initialized to pseudo random number.

I have made the change in the Sikkmod SDK to make it always initialized to a pseudo random number.

/*
===================
idGameLocal::LoadMap

Initializes all map variables common to both save games and spawned games.
===================
*/
void idGameLocal::LoadMap( const char *mapName, int randseed ) {

...
...
...

// reset the random number generator.
// random.SetSeed( isMultiplayer ? randseed : 0 );
// Solarsplace - 6th Nov 2014 - Always reset the random number generator. Not just for multiplayer.
random.SetSeed( randseed );


There is a new game00.zip attached. Extract it into your mod folder there is the game00.pk4 inside. I recommend you backup your old game00.pk4 in case this does not work out and you want to revert.

Thanks




The Happy Friar

#22
"Way back when" I did lots of test with random numbers, float vs int.  Float had a lot less randomness vs int for most proposes.  I'd print the random numbers to the console and they would be different sets each time I'd run the map.  When I tested float vs int, I never got 1.0 or 0.  When I tested ints (with the script I posted) I'd get 0 and 21 after a couple runs. 

My theory is this: int avoid rounding problems, float's don't.  If you want a random int between 0 and 21, there's only 22 possibilities.  if you get a float it's truncated or rounded (I forget).  If you want a random float between 0 and 1 & D3 supports 6 decimal points(not sure), there's 1 million possibilities.  People just don't normally think down to that level of detail when wanting a random number, but, in theory, a number between 0 and 21 has the same possibility as a float between 0 and .000021.  I want to say that even if I tried to get a random number between 0 and 21 and didn't use an int it always tended to be on the low end though (under 1).  I'd have to run tests again to see.

On another randomness note, you could use game time to help random numbers along with more "randomness".  I would figure because odds are you'd never take the exact same amount of time to get to the trigger that calls the script.  Might be 0.001234 seconds different, but that's a different number then 0.000000.

EDIT: currenttime - (int(currenttime)) should trunk the time so you get just a decimal & no whole number.  IE 234.534534 = current time, int(234.534534) = 235 (if it rounds).  That would leave -0.534534 as your value.  That could be a random value of it's own!

solarsplace

#23
Quote from: calan on November 05, 2014, 05:18:49 PM

...snip

For some reason, the random values assigned to each monster increase in an almost linear fashion as each monster is created.

... snip ...

Why would the random numbers increase as the monsters are being created? (It almost has to be a function of the RNG not being reseeded on each call I think)

snip ...


Hi

This turned into quite an interesting topic :)

Happy Friar makes some good points about the use of int vs float and that is something to bear in mind that is quite easily overlooked.

Calan is also quite right about the random seed being the issue for scripts that run random at map load. This is why you see the numbers increasing in a repeatable way each frame. Because the random seed is initalised to 0 and not a pseudo random in the beginning.

ID_INLINE int idRandom::RandomInt( void ) {
seed = 69069 * seed + 1;
return ( seed & idRandom::MAX_RAND );
}


So basically you probably wont notice this lack of randomness if you call random from various triggers in the map that happen at un-predictable times. But if you call random a lot on map load its going to be repeatable and quite un-random.

Cheers

solarsplace

Hi

Sorry to keep going on about this. But I wanted to fully understand what was happening here as I was quite interested in putting something like this in the Arx mod too.

These findings demonstrate the repeatability of calling random from scripts and code during map load when the seed is initalised to 0. This means that the same monsters will always either show or get removed every time!

Without the SDK fix We would need to find some other way of making this more random.

Run 1 of 2
Load D3, run test map, dump results:

rndNumber = 0.546204
rndNumber = 0.220947
rndNumber = 0.074280
rndNumber = 0.997070
rndNumber = 0.625793
rndNumber = 0.734131
rndNumber = 0.950745
rndNumber = 0.182129
rndNumber = 0.799133
rndNumber = 0.091064
rndNumber = 0.920959

Quite D3 entirely.

Run 2 of 2
Load D3, run test map, dump results:

rndNumber = 0.546204
rndNumber = 0.220947
rndNumber = 0.074280
rndNumber = 0.997070
rndNumber = 0.625793
rndNumber = 0.734131
rndNumber = 0.950745
rndNumber = 0.182129
rndNumber = 0.799133
rndNumber = 0.091064
rndNumber = 0.920959

Numbers exactly the same.

Implement SDK fix and repeat the above:

Run 1 of 2

rndNumber = 0.915375
rndNumber = 0.278839
rndNumber = 0.678802
rndNumber = 0.204376
rndNumber = 0.496918
rndNumber = 0.372101
rndNumber = 0.119720
rndNumber = 0.532013
rndNumber = 0.297211
rndNumber = 0.434113

Run 2 of 2

rndNumber = 0.172577
rndNumber = 0.386627
rndNumber = 0.728973
rndNumber = 0.823883
rndNumber = 0.715057
rndNumber = 0.878326
rndNumber = 0.880829
rndNumber = 0.299957
rndNumber = 0.976288
rndNumber = 0.838776

Numbers totally different this time!

Hope this helps someone out in the future.

Thanks

The Happy Friar

Try using my time adding idea and see what the spread is on those random numbers.

calan

First, thanks so much for the SDK fix Solar.  ;)

Second...  when I load a map using this in my script:


...
...
rndNumber = int(sys.random(100));
sys.warning( "random: " + rndNumber);
...
...


I now get mostly decreasing integers. The first half of the monsters get a random value above 50, the second half below. :wtf:
Old bastard but kid at heart...

solarsplace

Hi

Try using this to get a cleaner log:

rndNumber = int( sys.random( 100 ) );
sys.print( "random: " + rndNumber + "\n");


Not sure what is going on for you as I'm getting a pretty random spread:

random: 31
random: 25
random: 91
random: 6
random: 97
random: 13
random: 76
random: 32
random: 64
random: 71

Have you misplaced a = when it should be a == somewhere in the script?

Post your script as you have it, thanks.

calan

#28
This is the script as it sits now:



/*
=====================
monster_base::monster_begin
=====================
*/
void monster_base::monster_begin() {

// ***************************************  CAS random non-encounters begin  **********************************
float spawnControl = 0;
float rndNumber = 0;
spawnControl = getFloatKey( "spawn_control" );

if (spawnControl == 0 ) {
// No chance of spawning, just remove.
self.remove();
sys.print( "spawnControl = 0; monster removed\n");
return;
} else if (spawnControl == 1) {
// always spawn
sys.print( "spawnControl = 1; monster spawned\n");
} else if (spawnControl == 2) {
// spawn, then kill
self.hide();
self.kill();
sys.print( "spawnControl = 2; monster spawned and killed\n");
return;
} else if (spawnControl == 3) {
// randomized.appearances
rndNumber = int(sys.random(100));
if (rndNumber >= 50 ) {
// spawn
sys.print( "spawnControl = 3; monster spawned (random: " + rndNumber + ")\n");
} else {
// spawn, then kill
self.hide();
self.kill();
sys.print( "spawnControl = 3; monster spawned and killed (random: " + rndNumber + ")\n");
return;
}
}
// ***************************************  CAS random non-encounters  end  ***********************************

float teleportType;
string triggerAnim;
        ...
        ...
        ...



"spawn_control" is always "3", as set in the monster_default.def file.

I'm not sure the "spawn, then kill" approach is working. I believe it should finish the begin procedure and then kill it, but not sure. I've tried it both ways and haven't noticed any difference other than the visibility of the monster. (it stays visible and then commits suicide if moved to the end of the function.  :)  )

Regardless, the sikkmod random encounters doesn't seem to be getting triggered by the monster deaths as I had hoped.




EDIT: It does appear to be pretty random now. I dunno... maybe something wasn't saved or refreshed.

(thanks for the sys.print tip BTW.  ;) )

Now if I could just get my suicidal monsters to spawn new random ones....
Old bastard but kid at heart...

solarsplace

Hi

Glad you are getting more random results now  ;D

I just tried the .DLL I sent you with your code in this D3 test map testmaps/test_lotsaimps and got pretty random results indeed!

spawnControl = 3; monster spawned (random: 98)
spawnControl = 3; monster spawned (random: 66)
spawnControl = 3; monster spawned and killed (random: 9)
spawnControl = 3; monster spawned and killed (random: 18)
spawnControl = 3; monster spawned and killed (random: 17)
spawnControl = 3; monster spawned and killed (random: 39)
spawnControl = 3; monster spawned and killed (random: 13)
spawnControl = 3; monster spawned and killed (random: 40)
spawnControl = 3; monster spawned (random: 68)
spawnControl = 3; monster spawned (random: 80)
spawnControl = 3; monster spawned and killed (random: 17)
spawnControl = 3; monster spawned (random: 92)
spawnControl = 3; monster spawned (random: 98)
spawnControl = 3; monster spawned and killed (random: 25)
spawnControl = 3; monster spawned (random: 66)
spawnControl = 3; monster spawned (random: 99)
spawnControl = 3; monster spawned and killed (random: 38)
spawnControl = 3; monster spawned (random: 64)
spawnControl = 3; monster spawned (random: 59)
spawnControl = 3; monster spawned and killed (random: 31)
spawnControl = 3; monster spawned (random: 61)
spawnControl = 3; monster spawned (random: 53)
spawnControl = 3; monster spawned (random: 80)
spawnControl = 3; monster spawned (random: 79)
spawnControl = 3; monster spawned (random: 82)
spawnControl = 3; monster spawned (random: 64)
spawnControl = 3; monster spawned (random: 59)
spawnControl = 3; monster spawned (random: 92)
spawnControl = 3; monster spawned (random: 99)
spawnControl = 3; monster spawned (random: 85)
spawnControl = 3; monster spawned and killed (random: 10)
spawnControl = 3; monster spawned and killed (random: 43)
spawnControl = 3; monster spawned and killed (random: 34)
spawnControl = 3; monster spawned (random: 50)
spawnControl = 3; monster spawned (random: 61)
spawnControl = 3; monster spawned (random: 96)
spawnControl = 3; monster spawned and killed (random: 29)
spawnControl = 3; monster spawned (random: 80)
spawnControl = 3; monster spawned and killed (random: 39)
spawnControl = 3; monster spawned and killed (random: 20)
spawnControl = 3; monster spawned and killed (random: 5)
spawnControl = 3; monster spawned (random: 74)
spawnControl = 3; monster spawned and killed (random: 11)
spawnControl = 3; monster spawned (random: 99)
spawnControl = 3; monster spawned (random: 52)
spawnControl = 3; monster spawned (random: 77)
spawnControl = 3; monster spawned (random: 82)
spawnControl = 3; monster spawned (random: 92)
spawnControl = 3; monster spawned and killed (random: 35)
spawnControl = 3; monster spawned and killed (random: 37)

I have no idea how the Sikkmod random monsters thing works. I will have a look tomorrow and see if we can do something to get it to work.

By the way, you cannot put the kill code at the end of the monster_begin method because there are a number of wait states and loops that the AI sits in before it gets to the end of the method. So it may well sit there waiting to see you or be triggered before it ends itself.

Cheers for now.