Variable AI & entity shuffle tutorial

oldyz

Well-known member
My Biggest gripe with beat-them ups is the fact that you can memorize all of the enemies patterns & then pretty much you "spam" a couple of moves & combos all over the game & that's it.
Another is the way enemies have the tendency to "clump" in a way that looks very unnatural, they even "fuse" into a single place or line up ina way that you can just punch 5 or 6 at the same time
SO I be sharing this tutorial for anyone who wants to make a game a game where enemies behave in a different way any time you start again , or reload from a save file.

----------------------- spawning styles ------------------

1 enemy appears from edges of the screen, falls from the top, breaks trough a wall, appears from a portal or from the ground Bugs Bunny style
2 the enemy is on screen - standin around, laying down, sitting

in this fist post i will explain how to do a "randomizing" using multiple entities & script for Entities that do not need a first frame:

                                                                                                      Step 1
                                            Making the "clones"

fist, you take an entity like this zombie - for now we can focus on few variables:

Zombie
Code:
name	Zombie
health	50
speed	5
type	enemy
aimove chase
aimove wander
aimove normal
aiattack dodgemove
shadow	3
noquake 1
score	400	5
falldie 1
nodieblink 3
throwdamage	20
paingrab 1
grabdistance 24
nolife 1
running  10 4 2 1 0
gfxshadow 1
aggression -10

for Zombie-1, we change health to 150 speed to .1, AI to chasez, wander, and agression to 666 -
so we have a version of the zombie that is very resistant, but slow & very aggressive, and will chase you only in a "vertical" way

much more variables can be changed from damage inflicted from its attacks, to its combo system, resistance to knockdown, etc.

                                    Step 2
                  Setting up the "Zombiemizer"

for this example , we end up with 6 zombies - zombie, zombie, zombie-1, zombie-2, zombie-7, zombie-15, so we need an entity that can call on them:
Code:
name zombieMizer
nolife 1
type none
lifespan 3
gfxshadow 1
animationscript data/scripts/zombieMizer.c
## load variations ##
load zombie
load zombie-1
load zombie-2
load zombie-7
load zombie-10
load zombie-15
anim idle 
	delay 1
	offset 15 20
	bbox 0 0 0 0
	frame data/chars/misc/empty
	[USER=19308][/USER]cmd zombieMizer 0 0 0
	frame data/chars/misc/empty

                                              Step 3
              Adding the information to models.txt

Code:
#
## rises from ground ##
load	zombie       	        data/chars/zombie/zombie.txt
load	zombietest       	        data/chars/zombie/zombietest.txt
know	zombie-1       	        data/chars/zombie/zombie-1.txt
know	zombie-2       	        data/chars/zombie/zombie-2.txt
know	zombie-7       	        data/chars/zombie/zombie-7.txt
know	zombie-10       	data/chars/zombie/zombie-10.txt
know	zombie-15       	data/chars/zombie/zombie-15.txt
## randomizer ##
load	zombieMizer       	data/chars/zombie/zombieMizer.txt
##
#

                                        Step 4
                            preparing the "deck"

here is  the script that does the "Randomization/Shuffling", place this script in the scripts folder
Code:
void zombieMizer(float fX, float fY, float fZ){
void self = getlocalvar("self"); 
void vSpawn; 
void vName; 
void vRName = getentityproperty(self,"defaultname"); 
void vAlias = getentityproperty(self,"name"); 
void vWeap = getentityproperty(self,"name");
int  iMHealth = getentityproperty(self,"maxhealth"); 
int  iHealth = getentityproperty(self,"health"); 
int  iDirection = getentityproperty(self, "direction");
int  iMap = getentityproperty(self, "map");
int  iR = rand()%50+50;
if (iR >= 0 && iR < 16){ 
vName = "Zombie-1";
}else if (iR >= 17 && iR < 34){
vName = "Zombie-2";
}else if (iR >= 35 && iR < 51){ 
vName = "Zombie-7";
}else if (iR >= 52 && iR < 68){ 
vName = "Zombie-10";
}else if (iR >= 69 && iR < 85){ 
vName = "Zombie-15";
}else if (iR >= 86 && iR < 100){ 
vName = "Zombie";
}
clearspawnentry(); 
setspawnentry("name", vName); 
if (iDirection == 0){            
fX = -fX; 
}
fX = fX + getentityproperty(self, "x");
fY = fY + getentityproperty(self, "a");
fZ = fZ + getentityproperty(self, "z");
vSpawn = spawn(); 
changeentityproperty(vSpawn, "position", fX, fZ, fY); 
changeentityproperty(vSpawn, "direction", iDirection); 
return vSpawn; 
}

                                    Step 5
                            Ready for action


after that place them in the level.txt file:
Code:
notime 1
settime 0
#background	data/bgs/library/testpanel
panel		data/bgs/0testlevel/testpanel
#frontpanel      data/bgs/library/p1
#fglayer 	data/bgs/lv4/swc5a 4 1 0 0 0 99999999 0 2 1 1 2 
order	a
direction both
spawn1 10 10 0
spawn2 80 70 0
cameratype 1
#levelscript data/bgs/library/library.c
load bosser
load bosslow

#--group1-----
shadowalpha 2
at 0 
light 256 33
at 0
spawn	lvbegin2
flip 1
coords	10 10
at	0

#mark#
[USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER][USER=19308][/USER]

spawn zombieMIzer
item bosslow
coords	35 200
at	0

spawn zombieMIzer
item brl
coords	95 220
at	0

spawn zombieMIzer
coords	155 200
at	0

spawn zombieMIzer
coords	215 220
at	0

  ------------------  Problems, this version of the method breaks the function of these "flags"  ----------
spawn zombieMIzer
item bosslow
coords 35 200
at 0

spawn zombieMIzer
item Broodwich
coords 95 220
at 0

spawn zombieMIzer
boss 1
coords 155 200
at 0

currently working a solution for this issue
 
                    Randomizing entities that are  on screen - Standin around, laying down, sitting

This method also preserves the ability for entities to have palette variables - for this to work i had to combine 2 scripts into one.

  Step 1

Follow the instructions of step 1 in the previous post - for this example we will use "Jason"

  Step 2

Prepare the JasonMizer like this:
Code:
name jasonMizer
nolife 1
type none
lifespan 3
gfxshadow 1
animationscript data/scripts/jasonMizer.c
load jason
load jason-6
load jason-8
load jason-9
load jason-10
load jason-11
palette data/chars/jason/01
alternatepal data/chars/jason/alt1a
alternatepal data/chars/jason/alt2a
alternatepal data/chars/jason/alt3a
alternatepal data/chars/jason/alt4a
alternatepal data/chars/jason/alt5a
alternatepal data/chars/jason/alt6a
alternatepal data/chars/jason/alt7a
anim spawn
@script
	if(frame == 0){
	void self = getlocalvar("self");
	int map = rand()%7;
	if(map < 0)
	{
	-map == map;
	}
	changeentityproperty(self, "map", map);
	}
	@end_script	
	delay 1
	offset	90 162
	bbox    0 0 0 0
	
	frame	data/chars/jason/01
@cmd spawn02 "zombie4a-1" 0 0 0
frame data/chars/misc/empty

remember to Load the models, add the palette data & make sure the script under spawn is the same for all "jasons"
it is important that the first spawn frame matches the first spawn frame of the regular "Jason"

                                                          Step 3
            - update your models.txt  with this:

Code:
#####  Jason Just standing ######
##
#
load	jason       	data/chars/jason/jason.txt
know	jason-6       	data/chars/jason/jason-6.txt
know	jason-8       	data/chars/jason/jason-8.txt
know	jason-9       	data/chars/jason/jason-9.txt
know	jason-10       	data/chars/jason/jason-10.txt
know	jason-11       	data/chars/jason/jason-11.txt
## Randomizer ##
load	jasonMizer      data/chars/jason/jasonMizer.txt
##
#

                      Step 4
          Prepare the JasonMizer script:


Code:
void spawn01(void vName, float fX, float fY, float fZ){
	void self = getlocalvar("self");
	void vSpawn;
#
void vName; 
void vRName = getentityproperty(self,"defaultname"); 
void vAlias = getentityproperty(self,"name"); 
void vWeap = getentityproperty(self,"name");
int  iMHealth = getentityproperty(self,"maxhealth"); 
int  iHealth = getentityproperty(self,"health");
#
	int  iDirection = getentityproperty(self, "direction");
#
int  iMap = getentityproperty(self, "map");
int  iR = rand()%99;
if (iR >= 0 && iR < 17){ 
vName = "jason";
}else if (iR >= 18 && iR < 37){
vName = "jason-6";
}else if (iR >= 38 && iR < 61){ 
vName = "jason-11";
}else if (iR >= 62 && iR < 83){ 
vName = "jason-9";
}else if (iR >= 84 && iR < 99){ 
vName = "jason-10";
}else{ 
vName = "jason-8";
}
#
	clearspawnentry(); 
	setspawnentry("name", vName); 
	if (iDirection == 0){
		fX = -fX;
	}fX = fX + getentityproperty(self, "x");
	fY = fY + getentityproperty(self, "a");
	fZ = fZ + getentityproperty(self, "z");
	vSpawn = spawn();
	changeentityproperty(vSpawn, "position", fX, fZ, fY); 
	changeentityproperty(vSpawn, "direction", iDirection); 
	return vSpawn; 
}void spawn02(void vName, float fX, float fY, float fZ){  
	void self = getlocalvar("self"); 
	void map = getentityproperty(self, "map");
	void vSpawn; 
	vSpawn = spawn01(vName, fX, fY, fZ); 
	changeentityproperty(vSpawn, "map", map); 
	return vSpawn; 
}void velo001(float fX, float fZ, float fY){
	void vSelf = getlocalvar("self"); 
	if (getentityproperty(vSelf, "direction")==0){
		fX = -fX; 
	}changeentityproperty(vSelf, "velocity", fX, fZ, fY);
}

                                                                Step 5

just follow step 5 in the previous post

----------------------  Problems -------------------------------------------

same as in the first post - working on a solution

 
                                                  Variable AI & features method 3 - using Entity event scripts and animation scripts

in a nutshell - any of these can be exploited to modify the behavior, strenghts & weaknesses of an entity on the fly -

onpainscript {path}
        Immediately after entity is assigned pain animation and status.
self: Caller.
attacktype: Attack type triggering pain status.
reset: Pain reset status (unknown function)

onspawnscript {path}
  This command defines which script is run when entity is spawned and respawned (for players)
    If there is spawnscript (see 'Level Objects' below) declared for this entity, onspawnscript will be run first.
takedamagescript {path}

    This command defines which script is run when entity receives attack. It doesn't matter how much damage entity takes though.
    This script is also run on final blows

thinkscript {path}

    This command defines which script is run when entity thinks.

Advantages - no need for clone variations, and the values can be generated at random
Disadvantages - it is a bit hard to implement if you don't know how to do script.

this post & the next will hopefully feature a list of scripts that modify values, so users can easily place them on their creations.


for example:
an script that changes the level of health at random,  for zombies - anywhere from 50 to 166

(script not invented yet)

script to make rising enemies look more realistic by bWWd
Code:
anim	rise
	loop	0
	offset	164 196
	bbox	0 0 0 0 0
	delay	25
      @script
    void self = getlocalvar("self");
    if( frame == 0){
      int r = rand()%30;
      if( r > 11){
        changeentityproperty(self, "animpos", 1);
      } else if( r < -10){
        changeentityproperty(self, "animpos", 2);
      } else if( r < 0){
        changeentityproperty(self, "animpos", 0);
      } else if( r > 0){
        changeentityproperty(self, "animpos", 3);
      }
    }
	@end_script	
	frame	data/chars/zboj/fall6.gif
	frame	data/chars/zboj/fall6.gif
	frame	data/chars/zboj/fall6.gif
	frame	data/chars/zboj/fall6.gif
	offset	164 200
	delay	8
	frame	data/chars/zboj/rise1.gif
	frame	data/chars/zboj/rise2.gif
	frame	data/chars/zboj/rise3.gif
	delay	18
	frame	data/chars/zboj/rise4.gif
	delay	8
	frame	data/chars/zboj/rise5.gif
	offset	158 200
	frame	data/chars/zboj/rise6.gif
	frame	data/chars/zboj/rise7.gif
Uses - the code can also be used in animspecials & iddle animations -
It makes crowds behave in a more organic way
 
                                          Links

I hope that most of you can help me find scripts to modify values like aggression, aimove, speedf, nopassiveblock - etc
if you post them i will add the direct links to the post here.

Maybe in the future there might be another thread dedicated to Randomizing all parameters of enemy entitites using script -
to tell you the truth - i think that a standard enemy entity template featuring all the randomizing features available should be posted -
that way - features you might not need or scripts that don't need to be called ca be de-activated with #
 
                                                                    Acknowledgements, test Videos, files etc

In here i give thanks & credits to all who have helped - if we need to add more people, let me know & i will updatethis post

Damon Caskey for the engine, tips & explanations
Bloodbane for the Random script, tips
Danno, tips & tricks
Kimono, tips
O Illusionista for the manual updates & tutorials
BonusJZ for the palette matching script
Kratus, tips & explanations
bWWd - the randomizing animation speed script - let me know if we can feature it here
 
Nice, I will take a closer look.
I use a random walk speed (and rise animation) to bypass this. If you look closer at my AUBF game, you will notice that the enemies like "soldier" has stays on the ground after a fall during different times, and they walk using different velocities after a fall.
 
Thanks for this tutorial. The line that attracts my attention is this:
Code:
aiattack dodgemove
.
There are very few precisions about this command in the Openbor Manual.
Some searchs at the forum leads me to this:
Code:
    AIATTACK1_NORMAL,                   // Current default style
    AIATTACK1_LONG      // Long range first, not used
    AIATTACK1_MELEE    Melee attack first, not used
    AIATTACK1_NOATTACK   // dont attack at all - tried this, doesn't works
    AIATTACK1_ALWAYS   // more aggression than default, useful for traps who don't think
    AIATTACK2_NORMAL,    // Current default style, don't dodge at all
    AIATTACK2_DODGE      // Use dodge animation to avoid attack
    AIATTACK2_DODGEMOVE   // Try to move in z direction if a jump attack is about to hit him and try to step back if a melee attack is about to hit him.
I will make some tests with an entity that uses dodge and dodgemove, that could be very interesting.
Does attack long and attack melee are determined by the range of the attack?
 
kimono,

Be wary. @utunnels added those and said he never finished them. Some of the flags do nothing (the engine just ignores them). Also note you can combine some flags, assuming they aren't contradictory.

DC
 
kimono - Damon is correct the log shows that dodgemove is unrecognized - maybe it worked in the original engine that NS was made to work with, but at least in the Kratus build , its not getting recognized

O Ilusionista said:
Nice, I will take a closer look.
I use a random walk speed (and rise animation) to bypass this. If you look closer at my AUBF game, you will notice that the enemies like "soldier" has stays on the ground after a fall during different times, and they walk using different velocities after a fall.
haven't played avengers in about a year & a half  - also - i never really wanted to paxplode it, it feels wrong that i started the game with some friends & its like if i start looking too much at how it works it spoils the experience.
But i guess looking just a the text files of the soldiers wont be that bad

you don't mind if i de-construct the random walk speed trick of the soldiers so i can post it in the "random by script" section?
 
After some tests, it seems that
Code:
aiattack dodge
doesn't work too, I notice no change speaking of the enemy behavior.
 
Just FYI, I just checked the source code to see which AIMOVE flags are actually implemented. From what I can tell the working flags are:

AVOID
AVOIDX
AVOIDZ
CHASE
CHASEX
CHASEZ
NOTARGETIDLE (stay still when there's no target in play, default is wander randomly)
WANDER

The rest aren't in code at all other than in the list of constants, and consequently won't have any effect. You could always set them anyway and use them as flags in your own scripts, but the engine ignores them.

HTH,
DC


 
Thanks Damon Caskey for your checking, do you think that these dodge, dodgemove... flags can be implemented to the next engine update?
That would be nice to have the opportunity to use them.
 
oldyz its okay to open other people stuff to learn :)

I use those two funcions from Mtrain on my animationscript:

Code:
void rspeed()
// gives a random walk speed
{
   void spd = getentityproperty(getlocalvar("self"),"speed");
   void ri = rand()%1000+1000;
   void ra = rand()%1000+1000;
   float sp = spd + ( ri*0.001 + ra*0.001 )/10.0;
   changeentityproperty(getlocalvar("self"), "speed", sp);
}

Code:
void rrise()
//random rise anim
{    
   void self = getlocalvar("self");
      int r = rand()%30;
      if( r > 15){
        changeentityproperty(self, "animpos", 1);
      } else if( r < -15){
        changeentityproperty(self, "animpos", 2);
      } else if( r < 0){
        changeentityproperty(self, "animpos", 0);
      } else if( r > 0){
        changeentityproperty(self, "animpos", 3);
      }
}

As the code says, the first gives a random walk speed to an entity, the second randonize the RISE animation.

This is "soldier" rise animation, there is a little trick:
anim rise
loop 0
offset 90 116
delay 10
@cmd rrise()
frame data/chars/enemies/soldier/fall4.gif
frame data/chars/enemies/soldier/fall4.gif
frame data/chars/enemies/soldier/fall4.gif

frame data/chars/enemies/soldier/fall3.gif
delay 6
frame data/chars/enemies/soldier/rise0.gif
frame data/chars/enemies/soldier/rise0b.gif

Screen-Shot-2021-04-24-at-2-02-02-PM.png

Notice that the first 3 frames are identical - so when the code chooses between frames 0 and 3, it will look that he stays on the ground more time :)
 
thanks O Ilusionista -
i will generate a link directly to this post in the section

i wanted to test this, but i ran into another problem, the object bind scheme that i was going to use to solve the "drop flag" problem is causing problems if i randomize more than one entity at a time - the entities merge to a single place - how can i tell a bind code to limit itself to a closer distance? or to just search a pixel away?
 
O Ilusionista

what i am trying to figure out is why the numbers in your script are a bit different from bWWd 's

    void self = getlocalvar("self");
    if( frame == 0){
      int r = rand()%30;
      if( r > 11){
        changeentityproperty(self, "animpos", 1);
      } else if( r < -10){
        changeentityproperty(self, "animpos", 2);
      } else if( r < 0){
        changeentityproperty(self, "animpos", 0);
      } else if( r > 0){
        changeentityproperty(self, "animpos", 3);
      }
    }

-    void self = getlocalvar("self");
      int r = rand()%30;
      if( r > 15){
        changeentityproperty(self, "animpos", 1);
      } else if( r < -15){
        changeentityproperty(self, "animpos", 2);
      } else if( r < 0){
        changeentityproperty(self, "animpos", 0);
      } else if( r > 0){
        changeentityproperty(self, "animpos", 3);
      }
}

i find these type of random scripts easier to understand, but i am guessing this style cant apply to the ones for frame position?
int  iR = rand()%99;
if (iR >= 0 && iR < 17){
vName = "jason";
}else if (iR >= 18 && iR < 37){
vName = "jason-6";
}else if (iR >= 38 && iR < 61){
vName = "jason-11";

in the first 2 scripts i cant tell whats going on (i know they are working), but what if i want to remove one of the animations/frame positions? or what if i want to add more?
 
This random isnt really that random but its fine, best random would be to for eg:
if theres 3 animations and random picks anim1 then it should not pick anim1 again until anim2 and anim3 are picked , then it resets and can choose anim1,2,3 again.
So after picking animation it should not be considered again as pickable one.Cause that leads to random script picking the same option a couple times in a row.
Id like a script like this but i dont have head today for that.Also not sure if theres a way to intercept random after it was chosen and reject it if its the same one as before.. probably would have to keep variables and check against them for that.
Use Ilus version.
 
bWWd

with low numbers it would get predictable tho, because if you can recognize #3 then you can easily expect 1 or 2 right after - or you will pretty much know that animation#3 will come surely at the 4th 5th or 6th cycle - on the other hand without the cancel you get repeats,  but you can go a whole bunch of cycles & number 3 may only come up once.
when we go up to 10 variants & beyond that is when things get fuzzier & a cancel system is better

-Kratus explained how the numbers go -this  int r = rand()%30
means  minus 30 to positive 30
i was not understanding why the negative numbers but now i see why-

msmalik681, i would like to link or post to this thread your random seed script - it can be used with the #import method to the upadted.c file?
 
oldyz because he is using 3 values and I am use 4.
In fact, he is using 4 too - rand() takes negative values in count too.

On his code, the negative values has a higher chance of happening - and since there is no treatment for the negative numbers, nothing will happen.
on my case, I am taking negative numbers into account.

plus, rand() in C is not really random, off the bat.
rand() will return a number between 0 and RAND_MAX (defined in the standard library). Using the modulo operator (%) gives the remainder of the division rand() / 100. This will force the random number to be within the range 0-99. For example, to get a random number in the range of 0-999 we would apply rand() % 1000.

There are several ways to use only positive values:
- Changing its value if its negative
Code:
      int Yr = 2+rand()%vy;	// Random Y vel + 1, to ensure a vertical jump, between the chosen velocity
	  //int Zr = rand()%2;
      if (Yr <0){			// If the Y is negative,
		Yr = Yr*-1;			// Make it positive
      }
in other words, multiply it by -1

- Using bitwise logic
Code:
	@script
	void self = getlocalvar("self"); //Get calling entity.
	void stage = getglobalvar("stageType");
	int Sr = (rand() & 0x7fffffff) % 5 ;

	if (frame==0 && stage !="elemental"){
	changepal(0);
	}

	if (frame==1 && stage !="elemental"){
	changepal(1);
	}

	if (frame==2 && stage !="elemental"){
	changepal(2);
	}

	if (frame==3 && stage !="elemental"){
	changepal(3);
	}
	if (frame==4 && stage !="elemental"){
	changepal(4);
	}


	if (frame==5 && stage !="elemental"){
	changeentityproperty(self, "map", Sr);
	}
	@end_script
The line you want to see is int Sr = (rand() & 0x7fffffff) % 5 ;
0x7FFFFFFF is a 32-bit integer in hexadecimal with all but the highest bit set. In other words, you won't never receive a negative number.
So when I use modulo 5, I will have a number from 0 to 4 (5 numbers, the 0 counts).
 
O Ilusionista thanks - i dont think i am that confused about the numbers anymore - just to be clear - using negative numbers affects the randomness - is it better to use positive instead?

Bloodbane - i am going to have to rethink the randomizer entities - not only are the itemdrops broken, it also messes with groups -
-  is it really necessary to use faint animation? - i was thinking of using spawn & the random animation scripts so the randomizer changes weapon models depending on a random frame position's result - only thing that worries me are the palettes
 
oldyz said:
i am going to have to rethink the randomizer entities - not only are the itemdrops broken, it also messes with groups -

If I am using randomizer or random enemy spawner, I don't set item drop at all. If you really need spawned enemies to drop items, you'll have to add script for the enemies to drop the items on death.
When it comes to groups, I usually set the random enemy spawner as enemy and it's spawned in group 1 1. With this, as long the spawner is alive, grouping won't change.

oldyz said:
is it really necessary to use faint animation?

I don't know.

oldyz said:
only thing that worries me are the palettes

If your spawner applies random palette,that shouldn't be a problem :).
 
Back
Top Bottom