New band member AI code

posted in Caveman
Published September 18, 2014
Advertisement
New band member AI code

Here's the new code for bandmember AI.

In AI terms, this code is a crude expert system:

http://en.wikipedia.org/wiki/Expert_system

it is implemented primarily as a decision tree:

http://en.wikipedia.org/wiki/Decision_tree

// ############################## NEW AI: B2 API ######################### // stand is the default AI state#define B2_AISTATE_STAND 0#define B2_AISTATE_REST 1#define B2_ORDERS_NONE 0#define B2_ORDERS_FOLLOW 1#define B2_ORDERS_GOTO 2#define B2_ORDERS_ATTACK 3#define B2_ORDERS_FLEE 4#define B2_ORDERS_MAINTAIN_DIST 5 // ------------------- BAND MEMBER AI ----------------------------// level 7 void init_bandmember_CR(int a){int t, // bandmember's target rh; // rel heading to tgtif (cm[a].collision_recovery) { cm[a].collision_recovery=0; return; }cm[a].collision_recovery=1;cm[a].CR_counter=0;t=cm[a].tgt;if (t != -1) // if have tgt { rh=RH(cm[a].mx,cm[a].mz,(int)cm[a].x,(int)cm[a].z,(int)rad2deg(cm[a].yr),animal[t].mx,animal[t].mx,(int)animal[t].x,(int)animal[t].z); if (rh>0) // if tgt to right { if (dice(10000) < 7500) cm[a].CRyr=cm[a].yr+pi*0.75f; // 75% chance to turn right else cm[a].CRyr=cm[a].yr+pi*1.25f; // 25% chance to turn left } else // tgt to left { if (dice(10000) < 2500) cm[a].CRyr=cm[a].yr+pi*0.75f; // 25% chance to turn right else cm[a].CRyr=cm[a].yr+pi*1.25f; // 75% chance to turn left } }else // no tgt { if (dice(10000) < 5000) cm[a].CRyr=cm[a].yr+pi*0.75f; // 50-50 chance of left or right else cm[a].CRyr=cm[a].yr+pi*1.25f; }while (cm[a].CRyr >= pi*2.0f) cm[a].CRyr -= pi*2.0f; // nomralize to 0 to 2pi} void B2_BM_turn(int a,float amount){// a is band member #// amount is amount to turn (in rads). postive = turn right, negative = turn left.cm[a].yr+=amount;while (cm[a].yr<0.0f) { cm[a].yr+=pi*2.0f; }while (cm[a].yr>=pi*2.0f) { cm[a].yr-=pi*2.0f; }} // ------------------------------------------------------ // level 6 float B2_BM_calc_amount2move(int a){// a is BM #// w is curwpn// t is tgt typeint w,desired_rng,t;float maxspd,amount;// move 0.0 if no tgtif (cm[a].tgt == -1) { return(0.0f); }w=cm[a].curwpn;if (object[w].ishandwpn) { t=animal[cm[a].tgt].type; desired_rng=animaltype[2].rad+animaltype[t].rad; }else { desired_rng=50; }amount=(float)(cm[a].rng-desired_rng);maxspd=movement_rate(a);// multiply movement rate by two for runningmaxspd*=2.0f;if (amount < -maxspd) { amount=-maxspd; }if (amount > maxspd) { amount=maxspd; }return(amount);} void B2_BM_move(int a,float amount){// a is BM #float x,y,z,spd,vx,vz;int mx,mz, // moved,tgt,t,rh, // move_type, // 0=move atk. 1= move follow 2 = dont move old_mx,old_mz; // , // b; // who to followlocation L;if (cm[a].falling) { return; }mx=cm[a].mx;mz=cm[a].mz;x=cm[a].x;y=cm[a].y;z=cm[a].z;old_mx=cm[a].mx;old_mz=cm[a].mz;spd=amount; // movement_rate(a)*2.0f;vx=sin(cm[a].yr)*spd;vz=cos(cm[a].yr)*spd;x+=vx;z+=vz;normalize_location(&mx,&mz,&x,&z);y=heightmap(mx,mz,x,z);L.mx=mx;L.mz=mz;L.x=x;L.y=y;L.z=z;if ( (cm[a].location==OUTSIDE) && (! inpermshel(a)) && (! in_rockshelter(a)) && (terrain_in_way(&L)) ) { init_bandmember_CR(a); return; }else if ((cm[a].location==INCAVERN) && (cavern_wall_in_way(&L))) { init_bandmember_CR(a); cm_standani(a); return; }if (bandmember_in_way_of_bandmember(a,&L)) { init_bandmember_CR(a); cm_standani(a); return; }if (animal_in_way_of_bandmember(mx,mz,x,z)) { init_bandmember_CR(a); cm_standani(a); return; }if (bandmember_steep_rise_ahead(a)) // steep rise { init_bandmember_CR(a); cm_standani(a); return; }if (steep_drop(cm[a].y,y,spd)) // steep drop { cm[a].falling=1; cm[a].vy=0.0f; y=cm[a].y; }cm[a].x=x;cm[a].y=y;cm[a].z=z;cm[a].mx=mx;cm[a].mz=mz;cm[a].vx=vx;cm[a].vz=vz;automap(a,old_mx,old_mz,mx,mz,(int)x,(int)z);increase_fatigue(a);if (cm[a].camoflaged) { if ( cm[a].camotype != map[cm[a].mx][cm[a].mz].coverage ) { msg("The terrian has changed and you are no longer camoflaged!"); cm[a].camoflaged=0; } }cm[a].moved=1;} // returns rel heading from yr to desired_dir. all units are in rads.float B2_calc_rh(float yr,float desired_dir){float rh;rh=desired_dir-yr;while (rh>pi) { rh-=pi*2.0f; }while (rh<=-pi) { rh+=pi*2.0f; }return(rh);} // turn yr given desired_dir and maxturnrate. all units are in rads.void B2_turn_unit(float *yr,float desired_dir,float maxturnrate){float rh;rh=B2_calc_rh(*yr,desired_dir);if (rh > maxturnrate) { rh=maxturnrate; }if (rh < -maxturnrate) { rh=-maxturnrate; }*yr+=rh;while (*yr<0.0f) { *yr+=pi*2.0f; }while (*yr>=pi*2.0f) { *yr-=pi*2.0f; }} // returns heading from cm[a] to location L (in rads).float B2_BM_calc_heading2loc(int a,location *L){// a is bandmember// x,z is normalized x,z of tgt// h is heading in degrees// desired_dir is heading in radsfloat x,z,desired_dir;int h;normalize_coords(cm[a].mx,cm[a].mz,L->mx,L->mz,L->x,L->z,&x,&z);h=heading((int)cm[a].x,(int)cm[a].z,(int)x,(int)z);desired_dir=deg2rad((float)h);return(desired_dir);} void B2_BM_turn2loc(int a,location *L){// a is bandmemberfloat desired_dir;desired_dir=B2_BM_calc_heading2loc(a,L);B2_turn_unit(&cm[a].yr,desired_dir,animaltype[2].turnrate);} // -------------------------------------------------------- // level 5 // turns band member towards their tgt. tgt is an animal.// a is bandmember #. t is bandmembers tgt.void B2_BM_turn2tgt(int a){int t;location L;t=cm[a].tgt;L.mx=animal[t].mx;L.mz=animal[t].mz;L.x=animal[t].x;L.z=animal[t].z;B2_BM_turn2loc(a,&L);} int B2_BM_is_facing_tgt(int a){// a is BM #// t is tgt// x z is normalized location of tgt (tgt loc rel 2 cm[a] map sq )// rh is rel heading to tgtint t,rh;float x,z;t=cm[a].tgt;normalize_coords(cm[a].mx,cm[a].mz,animal[t].mx,animal[t].mz,animal[t].x,animal[t].z,&x,&z);rh=relheading( (int)cm[a].x, (int)cm[a].z, (int)rad2deg(cm[a].yr), (int)x, (int)z ); // normalized, ok.if (rh < -30) { return(0); }if (rh > 30) { return(0); }return(1);} void B2_BM_run(int a){// a is BM #float amount;amount=B2_BM_calc_amount2move(a);B2_BM_move(a,amount);if (cm[a].attacking) { B2_set_BM_atk_ani(a); }else { B2_set_BM_run_ani(a); }} void B2_BM_run_maxspd(int a){// a is BM #float amount;amount=movement_rate(a);// multiply movement rate by two for runningamount*=2.0f;B2_BM_move(a,amount);if (cm[a].attacking) { B2_set_BM_atk_ani(a); }else { B2_set_BM_run_ani(a); }} // 5 frames from start of attack to attack resolution// impact point is 4 ft from bandmember's head, in the direction of view.// dist travelled = (player run speed + animal run speed) * 5 frames // start attack range = animal rad + impact rng + dist travelledvoid do_bandmember_atk(int a){int tx,tz, // tgt x,z tgt, // bandmember's target rh, // rel heading to tgt t, // animaltype of target aniID;float start_attack_range, // range at which to begin attack tgtspd; // target's speedif (cm[a].attacking) return; tgt=cm[a].tgt;if (tgt == -1) return;t=animal[tgt].type;tgtspd=animaltype[t].speed;if (! animaltype[t].avian) tgtspd*=2.0f;start_attack_range=(movement_rate(a)*2.0f+tgtspd)*5.0f+4.0f+(float)animaltype[t].rad;if (cm[a].rng > start_attack_range) return;tx=(int)animal[tgt].x;tz=(int)animal[tgt].z;rh=RH(cm[a].mx,cm[a].mz,(int)cm[a].x,(int)cm[a].z,(int)rad2deg(cm[a].yr),animal[tgt].mx,animal[tgt].mz,tx,tz);if (abs(rh) > 45) return;cm[a].attacking=1;cm[a].attack_counter=0;aniID=get_attack_ani(cm[a].curwpn,cm[a].sex);setBMani(a,cm[a].modelID,aniID);} void set_bandmember_tgt(int a) // a is bandmember # // sets (animal) tgt. -1 if no tgt.{int b,rng,bestdist,besttgt,t;bestdist=100000;besttgt=-1;for (b=0; b.active) continue; if (! animal.alive) continue; switch (animal.state) { case SUBDUED: case CAPTURED: case TAMED: case FRIENDLY: case WARRIOR: case COMPANION: continue; } // skip if more than 1 map sq away if (animal.mx > cm[a].mx+1) continue; if (animal.mx < cm[a].mx-1) continue; if (animal.mz > cm[a].mz+1) continue; if (animal.mz < cm[a].mz-1) continue; rng=(int)dist3d2(cm[a].mx,cm[a].mz,cm[a].x,cm[a].y,cm[a].z,animal.mx,animal.mz,animal.x,animal.y,animal.z); if (rng > 200) continue; // dont target anything over 200 away t=animal.type; if ( (animal.dmg>0) && (animal.dmg >= animaltype[t].hp/2) && (animaltype[t].speed >= movement_rate(a)) ) continue; // dont target wounded fleeing animals you cant catch if ( (t != 2) && // if not caveman, (animaltype[t].AI==MAINTAIN_DISTANCE_AI) && // and AI maintains distance, (animaltype[t].speed >= movement_rate(a)) // and if they're too fast too catch... ) continue; // skip them. dont target them. if (rng=pi*2.0f) { desired_dir-=pi*2.0f; }B2_turn_unit(&cm[a].yr,desired_dir,animaltype[2].turnrate);} int badguys_nearby(int a) // a is band member #{int b,t;for (b=0; b.type; if (!animal.active) continue; if (!animal.alive) continue; if (animal.mx != cm[a].mx) continue; if (animal.mz != cm[a].mz) continue; if ( (t==2) && (animal.state==FRIENDLY) ) continue; if ( (t==2) && (animal.state==COMPANION) ) continue; if ( (t==2) && (animal.state==WARRIOR) ) continue; if (animal.state==TAMED) continue; if (animal.state==SUBDUED) continue; if (animal.state==CAPTURED) continue; if (animaltype[t].AI!=ATTACK_AI) continue; if ( dist ( (int)animal.x, (int)animal.z, (int)cm[a].x, (int)cm[a].z ) > 50 ) continue; return(1); }return(0);} // turns bandmember a towards bandmember cvoid B2_BM_turn2BM(int a,int c){location L;L.mx=cm[c].mx;L.mz=cm[c].mz;L.x=cm[c].x;L.z=cm[c].z;B2_BM_turn2loc(a,&L);} // -------------------------------------------------------------- // level 3 int B2_BM_cornered(int cm1){location L;float r;if (cm[cm1].location == INSHELTER) { return(0); }r=4.0f;L.mx=cm[cm1].mx;L.mz=cm[cm1].mz;L.x=cm[cm1].x+r;L.y=cm[cm1].y;L.z=cm[cm1].z;if (!something_in_way_of_location(&L)) { return(0); }L.x=cm[cm1].x-r;if (!something_in_way_of_location(&L)) { return(0); }L.x=cm[cm1].x;L.z=cm[cm1].z+r;if (!something_in_way_of_location(&L)) { return(0); }L.z=cm[cm1].z-r;if (!something_in_way_of_location(&L)) { return(0); }return(1);} void B2_BM_collision_recovery(int a){// a is bandmember// rh is rel heading to CRyr// amount is the amount to movefloat rh,amount;cm[a].CR_counter++;if (cm[a].CR_counter > 20) { cm[a].collision_recovery=0; }B2_turn_unit(&cm[a].yr,cm[a].CRyr,animaltype[2].turnrate);rh=B2_calc_rh(cm[a].yr,cm[a].CRyr);if ((rh>-pi/4.0f)&&(rh= BMhp(cm1)) { return(1); }return(0);} int B2_BM_tgt_is_stronger(int cm1){// returns 1 if tgts hp left is > 2x BMs hp leftint tgt,species,species_hp,tgt_hp,BM_hp;tgt=cm[cm1].tgt;species=animal[tgt].type;species_hp=animaltype[species].hp;tgt_hp=species_hp-animal[tgt].dmg;BM_hp=BMhp(cm1)-cm[cm1].dmg;BM_hp*=2;if (tgt_hp > BM_hp) { return(1); }return(0);} void B2_BM_move2player(int cm1){float amount;B2_BM_turn2BM(cm1,cm0);amount=movement_rate(cm1);amount*=2.0f;B2_BM_move(cm1,amount);} void doactionmodeB(int cm1){int a;a=7+cm[cm1].mood/13;if (frame<=a) action[cm[cm1].current_action].proceedure();} void B2_BM_follow(int cm1){// ordersdata[0] = tgt// ordersdata[1] = tgt_is_caveman// calc heading to tgt,// turn to tgt// if rng > 20, runfloat amount;int rng;if (cm[cm1].ordersdata[1] == 1) { B2_BM_turn2BM(cm1,cm[cm1].ordersdata[0]); rng=bm2bm_dist(cm1,cm[cm1].ordersdata[0]); }else { cm[cm1].tgt=cm[cm1].ordersdata[0]; B2_BM_turn2tgt(cm1); rng=a2bm_dist(cm[cm1].ordersdata[0],cm1); } if (rng > 20) { amount=movement_rate(cm1); amount*=2.0f; B2_BM_move(cm1,amount); }} void B2_BM_goto(int cm1){// ordersdata[0] = x// ordersdata[1] = z// calc heading to tgt// turn to tgt// if rng > 0, runfloat amount;int rng;location L;L.mx=cm[cm1].mx;L.mz=cm[cm1].mz;L.x=(float)cm[cm1].ordersdata[0];L.z=(float)cm[cm1].ordersdata[1];B2_BM_turn2loc(cm1,&L);amount=movement_rate(cm1);amount*=2.0f;rng=BBdist((int)cm[cm1].x,(int)cm[cm1].z,cm[cm1].ordersdata[0],cm[cm1].ordersdata[1]);if (rng > amount) { B2_BM_move(cm1,amount); }} void B2_BM_maintain_dist(int cm1){// if badgys_nearby, flee// else if have tgt, atk// else standif (badguys_nearby(cm1)) { B2_BM_flee(cm1); return; }if (cm[cm1].tgt != -1) { B2_BM_attack(cm1); }} void B2_BM_taking_fire_flee(int a){// a is band member #// rh is relative heading// amount is amount to movefloat amount,rh;B2_turn_unit(&cm[a].yr,cm[a].taking_fire_dir,animaltype[2].turnrate);rh=B2_calc_rh(cm[a].yr,cm[a].taking_fire_dir);// move BMif (((rh>-pi/4.0f)&&(rh 99%if (cm[cm1].fatigue > 99000) { cm[cm1].AIstate=B2_AISTATE_REST;// do nothing. fatigue is replenished elsewhere in update_bandmember. cm_standani(cm1); trace[2]=1; return(1); }// if restingif (cm[cm1].AIstate == B2_AISTATE_REST) {// if fatigue > 98% if (cm[cm1].fatigue > 98000) {// do nothing. fatigue is replenished elsewhere in update_bandmember. cm_standani(cm1); trace[2]=2; return(1); }// else stop resting else { cm[cm1].AIstate=B2_AISTATE_STAND; } }set_bandmember_tgt(cm1);if (B2_BM_cornered(cm1)) { B2_BM_attack(cm1); trace[2]=3; return(1); }if (cm[cm1].collision_recovery) { B2_BM_collision_recovery(cm1); trace[2]=4; return(1); }if (cm[cm1].taking_fire) { if (cm[cm1].tgt != -1) { B2_BM_attack(cm1); cm[cm1].taking_fire=0; trace[2]=5; return(1); } else { B2_BM_taking_fire_flee(cm1); cm[cm1].taking_fire_counter++; if (cm[cm1].taking_fire_counter >= 150) { cm[cm1].taking_fire=0; } trace[2]=6; return(1); } }if (B2_BM_halfdead(cm1)) { B2_BM_flee(cm1); trace[2]=7; return(1); }if (cm[cm1].tgt != -1) { if (cm[cm1].rng <= 20) { if (B2_BM_tgt_is_stronger(cm1)) { B2_BM_flee(cm1); trace[2]=8; return(1); } else { B2_BM_attack(cm1); trace[2]=9; return(1); } } }trace[2]=10;return(0);} void B2doaction(int cm1){int save;save=cm0;cm0=cm1;doactionmodeB(cm1);cm0=save;} void B2_BM_do_orders(int cm1){switch (cm[cm1].orders) { case B2_ORDERS_FOLLOW: B2_BM_follow(cm1); break; case B2_ORDERS_GOTO: B2_BM_goto(cm1); break; case B2_ORDERS_ATTACK: B2_BM_attack(cm1); break; case B2_ORDERS_FLEE: B2_BM_flee(cm1); break; case B2_ORDERS_MAINTAIN_DIST: B2_BM_maintain_dist(cm1); break; }} void B2_BM_leave_shelter(int cm1){if (cm[cm1].current_action != LEAVESHELTER) { setaction(cm1,LEAVESHELTER); }B2doaction(cm1);} // -------------------------------------------------------------------------- // LEVEL 1 (top level) 13 types of AI // 1 type of AI void B2_BM_run_AI(int cm1){int badguys;strcpy_s(tracestr[0],100,"leave shelter ticks");strcpy_s(tracestr[1],100,"do common ai ticks");strcpy_s(tracestr[2],100,"do common ai result");strcpy_s(tracestr[3],100,"bandmember");strcpy_s(tracestr[4],100,"everything else ticks");if (cm1 == cm0) { return; }Zstarttimer(1);badguys=badguys_nearby(cm1);if (badguys) { if (cm[cm1].location == INSHELTER) { B2_BM_leave_shelter(cm1); return; } }trace[0]=Zelapsedticks(1);Zstarttimer(1);if (B2_BM_do_common_AI(cm1)) { trace[1]=Zelapsedticks(1); return; }trace[1]=Zelapsedticks(1);Zstarttimer(1);if (badguys) { if (cm[cm1].current_action != DONOTHING) { if (cm[cm1].current_action == MOVE) { stop_moving(cm1); } setaction(cm1,DONOTHING); } if (cm[cm1].orders != B2_ORDERS_NONE) { B2_BM_do_orders(cm1); return; } if (B2_BM_tgt_is_stronger(cm1)) { B2_BM_flee(cm1); return; } B2_BM_attack(cm1); return; }if (cm[cm1].current_action != DONOTHING) { B2doaction(cm1); return; }if (cm[cm1].orders != B2_ORDERS_NONE) { if (cm[cm1].location == INSHELTER) { B2_BM_leave_shelter(cm1); return; } B2_BM_do_orders(cm1); return; }// standcm_standani(cm1);trace[4]=Zelapsedticks(1);} // ############################## END NEW AI: B2 API #########################
Well, I had about 10 notes of interest about this code, but the editor clipped them! And i need to get back to work...


lets see... note that some orders are like AI states. B2_BM_run_AI calls badguys_nearby just once as an optimization. set_bandmember_target is only called if and when needed as another optimization. both iterate the entities list. a further optimization would be to iterate the entites list just once, get the index and range of the closest badguy, and then just use that info for badguys_nearby and set_bandmember_target. But for the moment it seems to run fast enough.
Previous Entry New bandmember AI done
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement
Advertisement
Advertisement