Jump to content

  • Log In with Google      Sign In   
  • Create Account

We're offering banner ads on our site from just $5!

1. Details HERE. 2. GDNet+ Subscriptions HERE. 3. Ad upload HERE.


adding new feature requires 99 code changes!


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
40 replies to this topic

#21 Norman Barrows   Crossbones+   -  Reputation: 2308

Like
1Likes
Like

Posted 16 September 2013 - 12:05 PM

Here's the C++ translation of band member update all:

//    must call move_rafts first!
void BMupdate_all()
{
    int a;
    for (a=0; a<num_bandmembers; a++)
    {
        if (cm[a].alive == 0)
        {
            continue;
        }
        //    do update stuff here - do global frame
        BMdecrease_fatigue(a);
        BMmodel_attack(a);
        BMmodel_surprise(a);
        BMmodel_full_fatigue(a);
        run_bandmember(a);
        BMmodel_raft_movement(a);
        BMmodel_paddling_fatigue(a);
        BMmodel_falling(a);
        BMmodel_sneak_detection(a);
        if (frame != 0)
        {
            continue;
        }
        //  do global second
        BMmodel_fatigue_dueto_damage(a);
        BMmodel_fatigue_dueto_encumbrance(a);
        BMrun_quests(a);
        BMclear_rockshelter_enccouter_flags(a);
        BMcheck_rockshelter_encounters(a);
        BMclear_cave_encounter_flags(a);
        BMcheck_cave_encounters(a);
        BMclear_hut_encouter_flags(a);
        BMcheck_hut_encounters(a);
        BMcheck_hut_takeover(a);
        BMcheck_climbing_animals(a);
        if (second != 0)
        {
            continue;
        }
        //    do global minute
        BMcheck_animal_encounters(a);
        BMcheck_caveman_encounters(a);
        BMmodel_intox(a);
        BMextinguish_torches(a);
        if (minute%7 == 0)
        {
            BMreduce_hygiene_dueto_movement(a);
        }
        if (minute%10 == 0)
        {
            BMreduce_water(a);
            BMreduce_sleep(a);
        }
        if (minute%15 == 0)
        {
            BMreduce_food(a);
            BMaffect_mood(a);
        }
        if (minute != 0)
        {
            continue;
        }
        //    do global hour
        BMmodel_dehydration(a);
        BMmodel_food_spoilage(a);
        BMmodel_exposure(a);
        BMmodel_heatstroke(a);
        BMmodel_drown_in_flood(a);
        BMmodel_wearNtear(a);
        BMmodel_perm_shelter_raids(a);
        BMmoodboost_nature_lover(a);
        if (hour%2 == 0)
        {
            BMmodel_damage_dueto_illness(a);
        }
        if (((hour>7)&&(hour<19)))
        {
            BMcheck_quest_encounters(a);
        }
        if (hour != 0)
        {
            continue;
        }
        //    do global day
        BMmodel_starvation(a);
        BMmodel_getting_sick(a);
        BMmodel_background_radiation(a);
        BMmodel_perm_shel_weathering(a);
        BMzero_god_relations(a);
        BMreduce_social(a);
        BMmodel_traps(a);
        BMmodel_skill_reduction(a);
    }
}


Edited by rip-off, 16 September 2013 - 04:10 PM.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


Sponsor:

#22 ApochPiQ   Moderators   -  Reputation: 16387

Like
0Likes
Like

Posted 16 September 2013 - 01:21 PM

Is it really necessary to do all those state checks and updates for *every* agent every tick?

#23 FLeBlanc   Crossbones+   -  Reputation: 3117

Like
0Likes
Like

Posted 16 September 2013 - 01:41 PM

Here's the C++ translation of band member update all:
 
 
// snipping code...


I think it's appropriate that this was in comic cans. Is it really possible that one agent could be doing all of that at once? Is it not possible to consolidate certain heartbeat actions (skill reduction, god relations, background radiation, illness damage, etc...) into a single update? Those affected members could all be stuck in a common structure for cache coherency, and affected in a single HeartbeatUpdate call. This, here, seems to me to be the very epitome of the phrase "spaghetti code". It's especially funny that you have a comment saying "Must call move_rafts first" but you actually call BMmodel_raft_movement sixth in line. What does the method run_bandmember() do, if it doesn't handle any of all this? This whole thing just screams for refactoring.


Edited by rip-off, 16 September 2013 - 04:12 PM.


#24 Norman Barrows   Crossbones+   -  Reputation: 2308

Like
1Likes
Like

Posted 16 September 2013 - 02:44 PM

Is it really necessary to do all those state checks and updates for *every* agent every tick?

 

 

 

 

excellent point.

 

technically, checks probably ought to be made as infrequently as possible.

 

as you can see, the code already splits up the checks into those done each frame, and those done once per game second, minute, hour, or day.

 

when implementing each type of new update, i would ask myself, how often does this need to be done?   the answer would usually indicate which of the general ranges of update frequency (each time step,  once per game second, game minute, etc)  seemed appropriate purely from a simulation point of view with no regard for performance.   this led to the general ordering  and slicing of things as seen above.

 

other than reducing the frequency of target selection from one per frame to once per second ( remember the thread about collision checks at 900x accelerate time? ),

i haven't gone back and re-evaluated the frequency at which things are being called. if / when needed, that could gain additional efficiency.

 

these are the updates done each time step:

BMdecrease_fatigue(a);
BMmodel_attack(a);
BMmodel_surprise(a);
BMmodel_full_fatigue(a);
run_bandmember(a);
BMmodel_raft_movement(a);
BMmodel_paddling_fatigue(a);
BMmodel_falling(a);
BMmodel_sneak_detection(a);

updating fatigue, doing the attack, incrementing surprise counters...    run_bandmember is the actual AI and move routine.   model_raft_movement just adds the raft's movement to the player's movement. falling is a special case move update, and of course LOS/LOF checks.

 

fatigue could probably be cut to once per second.

 

attack and movement obviously must be done every time step.

 

surprise lasts for so short a period of time (perhaps 2-3 seconds), that it too must be done every time step.

 

some savings might be found in the AI. But already i find the targeting a bit unresponsive at its current lower frequency (once per second as i recall).  you can close a lot of distance in a second in melee at sprint speed.

 

LOS/LOF checks for sneak mode could probably be cut to once per second, but again, you have a responsiveness issue similar to targeting.

 

i'll probably end up increasing targeting responsiveness some before the game is released.  perhaps to twice per second.  a half second delay in enemy reaction shouldn't give the player too much of an advantage.

 

side note:  why is it that every posing or email i write, i have to add a half dozen technical terms to the spellchecker (chrome's i assume)  ?

anybody else get that a lot?

 

anyway, as you say, a second look at how often things get called might be called for, and will probably be happening when i try to go for that true 900x accelerate time game speed. right now its probably only managing perhaps 30x or so on a baseline box (IE an entry level PC).  

 

and that will all be optimization of input() and update(), not render, it doesn't get called!     should make for an interesting case study.  

 

doubt i'll get true 900x, but i'll see what i can do.  its not that bad right now,  8 hours of game time sleeping only takes about 7 seconds of real time, unless there's a lot of entities active in the simulation.  

 

as you can infer from that last statement, some interesting optimizations challenges probably await.   i actually almost look forward to it.   at last some real engineering to sink my brain into.   too much of programming is simply glorified word processing.

 

 

i have the timing test results from the two loop layouts.   i'll post those.   

 

next step is to hook up the findfirst/next iterator.      is there a zero additional overhead c++ construct i can use in place of that?

 

wait, a vector comes with an iterator right? but they're implemented like a template, w/ rtt checking.....   

 

 

 

 


Is it really necessary to do all those state checks and updates for *every* agent every tick?

 

 

another excellent point.

 

not only can the frequency of calls be reduced, but entities can be updated in round robin fashion as well.

 

a few of the updates do this already, as do the targeting routines.    they get called every time step, but use "unit index mod current frame #" to divvy up the targets into groups. with only one group processed per time step.  so the update function gets called every time step, but the update for a unit only occurs once per second. and only 1/fps of the units is updated per time step.


Edited by rip-off, 16 September 2013 - 04:13 PM.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#25 ApochPiQ   Moderators   -  Reputation: 16387

Like
0Likes
Like

Posted 16 September 2013 - 02:59 PM

I'm not talking about efficiency concerns at all. I mean does it actually make sense to do all those calls?

I'd probably structure it very differently, where each agent in the game world has a set of properties that can update over time. These can either be purely computed, or updated periodically via mutation. I'd tend towards preferring pure computation as much as practical, since it removes a lot of repetitive logic and cleanly encapsulates various concerns.

For things which absolutely need to have mutated state on a periodic basis, I'd implement a generic mechanism for triggering updates. You could make it as simple as a container of function pointers, where each function pointer is invoked in turn and given a pointer to the objects it needs to update. This could be trivially handled as a bitmask, where you set a bit for each update that needs to run.

For example:
 
enum UpdateTypes
{
    UPDATE_TYPE_MOVE,
    UPDATE_TYPE_HEAL_OVER_TIME,
    UPDATE_TYPES
};

typedef void (*UpdateFunction) (UpdatingObject * obj);

UpdateFunction Updaters[UPDATE_TYPES];
Updaters[UPDATE_TYPE_MOVE] = &MoveFunction;
Updaters[UPDATE_TYPE_HEAL_OVER_TIME] = &HealOverTimeFunction;

// Game loop
while(true)
{
    for(unsigned i = 0; i < UPDATE_TYPES; ++i)
    {
        for each updated object
        {
            if(object->UpdateFlags & (1 << i))
                (*Updaters[i])(object);
        }
    }
}

// Example updater
void HealOverTimeFunction(UpdatingObject* obj)
{
    obj->Health += 10;
}

You can also flip this around, and have each updating object store a container of update functions that should be called on it next tick. Then you just add an update function to that list every time you detect something needs updating, and dispatch those update calls across each object in your game loop:

for each game object
{
    for each update function in this object's update list
    {
        update_func(object);
    }
}

Edited by ApochPiQ, 16 September 2013 - 03:01 PM.


#26 Norman Barrows   Crossbones+   -  Reputation: 2308

Like
1Likes
Like

Posted 16 September 2013 - 03:16 PM


I think it's appropriate that this was in comic cans. Is it really possible that one agent could be doing all of that at once? Is it not possible to consolidate certain heartbeat actions (skill reduction, god relations, background radiation, illness damage, etc...) into a single update? Those affected members could all be stuck in a common structure for cache coherency, and affected in a single HeartbeatUpdate call. This, here, seems to me to be the very epitome of the phrase "spaghetti code". It's especially funny that you have a comment saying "Must call move_rafts first" but you actually call BMmodel_raft_movement sixth in line. What does the method run_bandmember() do, if it doesn't handle any of all this? This whole thing just screams for refactoring.

 

<BG>

 

what you see is the result of simply taking the guts of all those update loops and placing each in its own function, then calling them all. many (perhaps 2/3's) are very short 1-3 liners.  further consolidation is almost definitely warranted.   in the mean time i have fastcall and inline any suitable on my side - not that that necessarily means a whole lot.

 

 OTOH, placing the body of the loop in its own function, and then placing the guts of all those routines (at least the smaller ones) in the loop body would have a negative impact on readability. not the loop body as a separate function, that actually makes things clearer - its the adding of all those little 1-3 liners to one big update_each() routine that would detract from the read-abilty of the loop body code.   

 

the general design considerations in software engineering: read-ability, write-ability, maintain-ability, modify-ability, reuse-ability, speed, resource usage, etc. are often at odds with each other.  

 

this is the nature of design considerations in general in all forms of engineering.  

 

thus all design is an exercise in compromise.  

 

good design finds that "sweet spot" trade-off point between competing considerations.

 

personally, i've always been big on read-ability.  i used to give team mates grief in school for using shift instead of add due to lack of readability (it was less English-like, and we didn't need the speed).


Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#27 Norman Barrows   Crossbones+   -  Reputation: 2308

Like
1Likes
Like

Posted 16 September 2013 - 03:31 PM


Those affected members could all be stuck in a common structure for cache coherency

 

 

rock shelter, cave, and hut encounter flags are in the list of caves, rock shelters, and huts.

 

everything else is in a caveman struct:

 

 

(gonna give slash code a try here....)

 
 
// cavemen list
struct cavemanrec
{
char name[100];                     
int str,intelligence,dex,con,chr,speed,   // core stats
food,water,sleep,mood,hp,dmg,has_disease,hygiene,     // current stats          
fatigue,   // fatigue * 1000
social,
xp[maxskilltypes],
mx,mz, // map sq             positive x is east. positive z is north.  x=0..4999.   z=0..4999.
active, // 0 or 1. whether the record is in use. 1=active.
current_action,     // what they're doing   0=do nothing.
action_counter,
action_x,
action_z,
moved,            // 1 if they moved this frame and dont recharge fatugue
wpndrawn,           // 1 if wpn drawn
sex,                 // 0=male 1=female
curwpn,             // what kind of weapon they're currently using      (an obj#)    
attacking,        // 0,1
attack_counter,
suprised,
suprise_counter,
using_stealth,
tracking_flag,   // 0=not using tracking. 1=using tracking to avoid encounters. 2=using tracking to find encounters.
running,          // 0 or 1
location,          // a pre-defined location. OUTSIDE, UPATREE, INSHELTER, etc
atk2subdue,        // 0 or 1
curammo,          // obj#   INACTIVE = no ammo selected
actiondata[10],
interests[3],
tgt,                 // used by clanmembers != cm0
rng,                //  rng to tgt
orders,              // used by clanmembers != cm0     0=no orders   1=follow me (ordersdata[0]=who to follow)
ordersdata[10],              // used by clanmembers != cm0     
has_perm_shelter,
shel_mx,
shel_mz,
shel_x,
shel_z,
paddling,
raftnum,
cur_quest,
god[8],
camoflaged,
camotype,
action_qual,
falling,
research_counter[maxskilltypes],
animals_killed,
cavemen_killed,
action_counter2[maxactions],
makeobj_counter[max_object_types],
stackptr,
perm_shel_quality,
aniplayerID,
currentani,
sneak_state,              // 0 not sneaking. 1 sneaking, undetected. 2 sneaking, detected.
collision_recovery,   // 0=not in collision recovery   1= in collision recovery
CR_counter,            // how many turns they've been in collision recovery
entangled,
climb_mode,
crh_index,         // which cave they are in
is_blocking,        // boolean
head_mesh,hair_mesh,skin_tex,face_tex,hair_tex,eye_tex,modelID,
alive;
 
float x,y,z,      // d3d location in the map sq.  pos x is east (0..4999).  pos z is north (0..4999).   pos y is up.
      xr,yr,zr,   // d3d orientation in radians
      vx,vy,vz,
      vxr,vyr,
      CRyr;          // desired heading for collision recovery
objrec2 stuff[maxobjrecs];
//psrec shel[psms][psms];
//psmap shel;
questrec quest[maxquests];
drugrec drug[maxdrugs];
traprec trap[maxtraps];
 
actionstackrec stack[actionstacksize];
 
};
 
 
 

Edited by Norman Barrows, 16 September 2013 - 03:34 PM.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#28 Norman Barrows   Crossbones+   -  Reputation: 2308

Like
0Likes
Like

Posted 16 September 2013 - 03:52 PM

ok, here's the results of the timing tests.

 

tests were conducted between run_simulation() as posted above, and the new run_simulation2() routine.

 

the new _runsimulation2 routine is identical, except that the many small player update loops have been replaced by one call to BMupdate_all at the beginning of do_global_frame.

 

 

to get meaningful numbers, i was required to measure the time in query performance counter ticks accumulated over of runs of 15 frames.

 

the old way with a bunch of loops clocked in at ~3000 ticks in 15 frames, or ~200 ticks per frame.

 

the new way with a single big loop clocked in at ~1000 ticks in 15 frames, or ~ 66 ticks per frame.

 

not surprising, perhaps a bit more difference than expected.     eliminating loop overhead helped.  not sure how much if any can be attributed to the code being more cache friendly.

 

the generic iterator is next, then consolidating all those one-liners.

 

so is there a zero overhead C++ alternative to a findfirst / findnext generic iterator design?


Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#29 rip-off   Moderators   -  Reputation: 8689

Like
0Likes
Like

Posted 16 September 2013 - 04:12 PM

Do not post code in blue comic sans on this forum, or anywhere on the Internet, ever again. We have [code] tags, use them.


Edited by rip-off, 16 September 2013 - 04:15 PM.


#30 Norman Barrows   Crossbones+   -  Reputation: 2308

Like
1Likes
Like

Posted 16 September 2013 - 04:24 PM


It's especially funny that you have a comment saying "Must call move_rafts first" but you actually call BMmodel_raft_movement sixth in line.

 

BMmodel_raft_movement simply moves the player by the same amount as the raft he/she's on.   gotta move the raft first, so you know how much to move the player!   <g>

 

thus there is a call order dependency  between move_rafts and BMmodel_raft_movement, and therefore between move_rafts (update rafts) and BMupdate_all (update PCs) in general.  

 

this is due to the design choice of consolidating the player movement due to rafts into BMupdate_all.      player movement due to rafts  ( BMmodel_raft_movement  )  is actually an "event handler" for a "raft moved" event.  

 

the old way with lots of loops processed the "raft moved" event immediately, which required a loop to iterate over the list of players to move them if they were aboard the raft.  but it was call order independent.   

 

the new way moves the raft and stores the resulting dx,dz movement from the event for later processing by BMupdate_all.  this eliminates the loop in move_rafts, and moves the body of that loop into a call from the loop body of BMupdate_all.  but its not call order independent any longer.    move_rafts must be called first to pickup the event, so BMupdate_all can then process it along with all the other band member updates in one big loop.

 

not sure which i favor.  i tend to like to process events immediately and not leave stuff hanging around.  it can often be simpler.   in this case all it got me was one consolidated loop and a call order dependency i didn't have before.   if call order becomes an issue i can always go back to processing the event immediately.


Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#31 Norman Barrows   Crossbones+   -  Reputation: 2308

Like
1Likes
Like

Posted 16 September 2013 - 04:27 PM


Do not post code in blue comic sans on this forum, or anywhere on the Internet, ever again. We have [code=auto:0] tags, use them.

 

 

i've been having trouble with "[ slash code ]" truncating everything after it in the post, thus the strange font.

 

sorry about that!


Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#32 Norman Barrows   Crossbones+   -  Reputation: 2308

Like
0Likes
Like

Posted 16 September 2013 - 07:11 PM

i've hooked up the generic "alive band member" iterator to the main loop in BMupdate_all.
 
i also put the body of the loop in its own function: BMupdate_each(int a);
 
then i collapsed all the calls in BMupdate_each to inline code to create a new version called BMupdate_each2
 
the iterators....
 
 

 
 

int BM_alive_num,BM_active_num;
 
 
int first_alive_bandmember()
{
int a;
for (a=0; a<maxcavemen; a++)
    {
    if (cm[a].active == 0)
        {
        continue;
        }
    if (cm[a].alive == 0)
        {
        continue;
        }
    BM_alive_num=a;
    return(a);
    }
return(-1);
}
 
 
 
 
int next_alive_bandmember()
{
int a;
for (a=BM_alive_num+1; a<maxcavemen; a++)
    {
    if (cm[a].active == 0)
        {
        continue;
        }
    if (cm[a].alive == 0)
        {
        continue;
        }
    BM_alive_num=a;
    return(a);
    }
return(-1);
}
 
 
 
 
 
 
 
 
int first_active_bandmember()
{
int a;
for (a=0; a<maxcavemen; a++)
    {
    if (cm[a].active == 0)
        {
        continue;
        }
    BM_active_num=a;
    return(a);
    }
return(-1);
}
 
 
 
 
 
 
 
 
int next_active_bandmember()
{
int a;
for (a=BM_active_num+1; a<maxcavemen; a++)
    {
    if (cm[a].active == 0)
       {
       continue;
       }
   BM_active_num=a;
   return(a);
   }
return(-1);
}
 
 
 

 
 
here's the new BMupdate_all:
 
 
 
 
//    must call move_rafts first!
void BMupdate_all()
{
int a;
a=first_alive_bandmember();
while (a!=-1)
    {
    BMupdate_each(a);
    a=next_alive_bandmember();
    }
}
 
 
 
 
and now, for a wall of spaghetti code - the new BMupdate_each2 with all the code pulled into one function.
 
 
this thing is probably similar  to that 32,000 line (or whatever it was) update function mentioned previously....
 
it seems to be bigger, uglier, and probably more work to maintain.    one function, 905 line of code.
 
 
 
 
 
 

void BMupdate_each2(int a)
{
int b,c,e,mx,mz,d,num,f,chance,rng;
float h,x,y,z;
char s[100];
//    do update stuff here - do global frame
//   decrease fatigue
if ((cm[a].moved==0) && (!cm[a].paddling) && (action[cm[a].current_action].energy))
    {
    cm[a].fatigue-=7;                               // reduce fatigue.
    if (cm[a].fatigue<0) cm[a].fatigue=0;
    }
//    model attack
if (cm[a].attacking) 
    {
    cm[a].attack_counter++;
    if (on_drug(a,GAHMUHROOT)) c=atktime*3/4; else c=atktime;
    if (cm[a].attack_counter >= c) 
        { 
        cm[a].attacking=0; 
        }
    else if (cm[a].attack_counter==5) resolve_attack(a);
    }
//   model surprise
if (cm[a].suprised)
    {    
    cm[a].suprise_counter++;
    if (cm[a].suprise_counter>60-xpbonus(cm[a].xp[HUNTING])) cm[a].suprised=0;
    }
//  model full fatigue
if (cm[a].fatigue >= 100000)
    {
    if (cm[a].current_action != MOVE) msg("You're so fatigued you have to stop and rest!");
    // dont resume the following actions w/ friendly cavemen, as they may have wandered off while band member was resting
    if ( (cm[a].current_action != TEACHCAVEMAN) && (cm[a].current_action != TELLJOKE) && (cm[a].current_action != TELLSTORY) )  pushaction2(a);   
    b=gamespeed;
    setaction(a,REST);
    gamespeed=b;
    cm[a].action_x=0;     // no bedding
    }
//   run bamdmember
if (a!=cm0) 
    {
    if (  badguys_nearby(a) && (cm[a].current_action != DONOTHING)  )        // halt action if badguys near
        {
        if (cm[a].current_action == MOVE) adjust_bandmember_location(a);
        setaction(a,DONOTHING);
        }
    if (cm[a].current_action==DONOTHING)
        {
        cm[a].is_blocking=0;
        if (cm[a].collision_recovery)
            {
            move_bandmember_collision_recovery(a);
            }
        else
            {
            set_bandmember_tgt(a);
            steer_bandmember(a);
            move_bandmember(a);
            do_bandmember_atk(a);
            }
        }
    else
        {
        b=cm0;
        cm0=a;
        doactionmodeB(a);
        cm0=b;
        }
    }
//     model raft movement
if (cm[a].location == ONRAFT)
    {
    if (cm[a].current_action != MOVERAFT)
        {
b=cm[a].raftnum;
        cm[a].x+=worldobj[b].dx;
        cm[a].z+=worldobj[b].dz;
        normalize_location( &cm[a].mx, &cm[a].mz, &cm[a].x, &cm[a].z );
        cm[a].y=heightmap(cm[a].mx,cm[a].mz,cm[a].x,cm[a].z);
        }
    }
//  model paddling fatigue
if (cm[a].paddling)
    {
    cm[a].fatigue+=1;
    if (cm[a].fatigue>100000) cm[a].fatigue=100000;
    }
//  model falling
if (cm[a].falling) 
    {
    cm[a].vy+=-g2;
    cm[a].x+=cm[a].vx;
    cm[a].y+=cm[a].vy;
    cm[a].z+=cm[a].vz;
    h=heightmap( cm[a].mx, cm[a].mz, cm[a].x,cm[a].z);
    if (cm[a].y <= h ) bandmember_hit_ground(a,h);
    }
//   model sneak detection
if (   (a % 15 == frame) && (cm[a].sneak_state!=0)  )
    {
    if (cm[a].sneak_state==3)
        {
        if (! bandmember_targeted(a)) cm[a].sneak_state=1;
        }
    else       // states 1 & 2
        {
        switch (detection_state(a))
            {
            case 0: cm[a].sneak_state=1;    //   sneaking - undetected
                    break;
            case 1: cm[a].sneak_state=2;     // sneaking - detected - non-hostile
                    break;
            case 2: cm[a].sneak_state=3;     // sneaking - detected - hostile
                    break;
            }
        }
    }
if (frame != 0)
    {
    return;
    }
//  do global second
//  model fatgue due to damage
if  (   
    (! on_drug(a,MUHAHBOOLEAF)) &&
    (! on_drug(a,YAHBEEBARK))   &&
    (! on_drug(a,BAHYUHSEED))   &&
    (! on_drug(a,GAHMUHROOT)) 
    )
    {
    cm[a].fatigue+=10*cm[a].dmg/cm[a].hp;
    if (cm[a].fatigue>=100000) cm[a].fatigue=100000;
    }
//  model fatigue due to encumberance
if (    (!on_drug(a,MUHAHBOOLEAF)) &&   (! on_drug(a,BAHYUHSEED))  )
    {
    cm[a].fatigue+=10*encumberance(a)/maxencumberance(a);
    if (cm[a].fatigue>=100000) cm[a].fatigue=100000;
    }
//  run quests
for (b=0; b<maxquests; b++)
    {
    if (! cm[a].quest[b].active) continue;
    run_quest(a,b);
    }
//  clear rock shelter encounter flags
mx=cm[a].mx;
mz=cm[a].mz;
if ( map[mx][mz].cliffs) 
    {
    for (e=60000; e<65000; e++)
        {
        if (crh2[e].mx != mx) continue;
        if (crh2[e].mz != mz) continue;
        if (crh2[e].encounter_checked)
            {
            if (first_bandmember_near_cave_or_rockshelter(mx,mz,e,300) == -1)
                {
                remove_CRH_cavemen(2,mx,mz,e);     // 2 = rockshelter housetype        removes friendly cavemen only!
                crh2[e].encounter_checked=0;
                if ((crh2[e].owner_type==HOSTILECAVE) && (hostile_CRH_encounter_underway) )
                    {
                    if ((! hostile_fled_CRH_encounter) && (! hostile_in_mapsq(a))) crh2[e].owner_type=EMPTYCAVE;
                    hostile_CRH_encounter_underway=0;
                    hostile_fled_CRH_encounter=0;
                    }
                }
            }
        }
    }
//   check rock shelter encounters
mx=cm[a].mx;
mz=cm[a].mz;
if ( map[mx][mz].cliffs)
    {
    for (e=60000; e<65000; e++)
        {
        if (crh2[e].mx != mx) continue;
        if (crh2[e].mz != mz) continue;
        if (crh2[e].encounter_checked) continue;
        if ( BBdist( (int)cm[a].x, (int)cm[a].z, crh2[e].x,crh2[e].z) > 75) continue;        // trigger at 75, reset at 300
        crh2[e].encounter_checked=1;
        switch ( crh2[e].owner_type )
            {
            case ANIMALCAVE: do_cave_animal_encounter(a); break;
            case FRIENDLYCAVE:  
                                if (crh2[e].npc_ownerID == -1)        // no npc assigned to rockshelter yet, so assign one
                                    {
                                    num=dice(10);
                                    for (b=0; b<num; b++)
                                        {
                                        x=crh2[e].x-20.0f+(float)b*10.0f;
                                        z=crh2[e].z+40.0f;
                                        y=heightmap(mx,mz,x,z);
                                        d=add_caveman(mx,mz,x,y,z,0,0);
                                        if (d != -1)
                                            {
                                            animal[a].butchered=new_npc();
                                            if (b==0) crh2[e].npc_ownerID=animal[d].butchered;
                                            npc[animal[d].butchered].house_type=2;    // house type: 0=unassigned 1=cave 2=rockshelter 3=hut
                                            npc[animal[d].butchered].house_mx=mx;
                                            npc[animal[d].butchered].house_mz=mz;
                                            npc[animal[d].butchered].house_index=e;
                                            animal[d].sacrificed=1;   // 1= they're a CRH caveman
                                            }
                                        }
                                    }
                                else
                                    {
                                    c=0;
                                    for (b=0; b<maxnpcs; b++)
                                        {
                                        if (! npc[b].active) continue;
                                        if (npc[b].house_type != 2) continue;       // if not rockshelter
                                        if (npc[b].house_mx != mx) continue;
                                        if (npc[b].house_mz != mz) continue;
                                        if (npc[b].house_index != e) continue;
                                        x=crh2[e].x-20.0f+(float)c*10.0f;
                                        z=crh2[e].z+40.0f;
                                        y=heightmap(mx,mz,x,z);
                                        d=add_caveman(mx,mz,x,y,z,0,0);
                                        if (d != -1)
                                            {
                                            animal[d].butchered=b;
                                            animal[d].sacrificed=1;   // 1= they're a CRH caveman
                                            }
                                        c++;
                                        }
                                    }
                                msg("You encounter cavemen from the nearby rockshelter!");
                                if (cm[a].current_action == MOVE) adjust_bandmember_location(a);
                                setaction(a,DONOTHING);
                                gamespeed=0;
                                break;
            case HOSTILECAVE:   add_hostile_cavemen(a,0); 
                                msg("You encounter cavemen from the nearby rockshelter!");
                                if (cm[a].current_action == MOVE) adjust_bandmember_location(a);
                                setaction(a,DONOTHING);
                                gamespeed=0;
                                hostile_CRH_encounter_underway=1;
                                hostile_fled_CRH_encounter=0;
                                break;
            case PLAYERCAVE: if (dice(10000)<=1000)  do_cave_animal_encounter(a); break;
            }
        }
    }
//    clear cave encounter flags
mx=cm[a].mx;
mz=cm[a].mz;
if ((map[mx][mz].elevation == 2) || (map[mx][mz].elevation == 3)) 
    {
for (f=0; f<numcaves[mx][mz]; f++)
    {
    e=caveindex[mx][mz][f];
        if (crh2[e].encounter_checked)
            {
            if (first_bandmember_near_cave_or_rockshelter(mx,mz,e,300) == -1)
                {
                remove_CRH_cavemen(1,mx,mz,e);     // 1 = cave housetype        removes friendly cavemen only!
                crh2[e].encounter_checked=0;
                if ((crh2[e].owner_type==HOSTILECAVE) && (hostile_CRH_encounter_underway) )
                    {
                    if ((! hostile_fled_CRH_encounter) && (! hostile_in_mapsq(a))) crh2[e].owner_type=EMPTYCAVE;
                    hostile_CRH_encounter_underway=0;
                    hostile_fled_CRH_encounter=0;
                    }
                }
            }
    }
    for (e=0; e<60000; e++)
        {
        if (crh2[e].mx != mx) continue;
        if (crh2[e].mz != mz) continue;
        if (crh2[e].encounter_checked)
            {
            if (first_bandmember_near_cave_or_rockshelter(mx,mz,e,300) == -1)
                {
                remove_CRH_cavemen(1,mx,mz,e);     // 1 = cave housetype        removes friendly cavemen only!
                crh2[e].encounter_checked=0;
                if ((crh2[e].owner_type==HOSTILECAVE) && (hostile_CRH_encounter_underway) )
                    {
                    if ((! hostile_fled_CRH_encounter) && (! hostile_in_mapsq(a))) crh2[e].owner_type=EMPTYCAVE;
                    hostile_CRH_encounter_underway=0;
                    hostile_fled_CRH_encounter=0;
                    }
                }
            }
        }
    }
//  check cave encounters
mx=cm[a].mx;
mz=cm[a].mz;
if ((map[mx][mz].elevation == 2) || (map[mx][mz].elevation == 3)) 
    {
for (f=0; f<numcaves[mx][mz]; f++)
    {
    e=caveindex[mx][mz][f];
        if (crh2[e].encounter_checked) return;
        if ( BBdist( (int)cm[a].x, (int)cm[a].z, crh2[e].x,crh2[e].z) > 75) continue;        // trigger at 75, reset at 300
        crh2[e].encounter_checked=1;
        switch ( crh2[e].owner_type )
            {
            case ANIMALCAVE: do_cave_animal_encounter(a); break;
            case FRIENDLYCAVE: 
                                if (crh2[e].npc_ownerID == -1)  // no npc assigned to cave yet, so assign one
                                    {
                                    num=dice(10);
                                    for (b=0; b<num; b++)
                                        {
                                        x=crh2[e].x-20.0f+(float)b*10.0f;
                                        z=crh2[e].z+40.0f;
                                        y=heightmap(mx,mz,x,z);
                                        d=add_caveman(mx,mz,x,y,z,0,0);
                                        if (d != -1)
                                            {
                                            animal[d].butchered=new_npc();
                                            if (b==0) crh2[e].npc_ownerID=animal[d].butchered;
                                            npc[animal[d].butchered].house_type=1;    // house type: 0=unassigned 1=cave 2=rockshelter 3=hut
                                            npc[animal[d].butchered].house_mx=mx;
                                            npc[animal[d].butchered].house_mz=mz;
                                            npc[animal[d].butchered].house_index=e;
                                            animal[d].sacrificed=1;   // 1= they're a CRH caveman
                                            }
                                        }
                                    }
                                else
                                    {
                                    c=0;
                                    for (b=0; b<maxnpcs; b++)
                                        {
                                        if (! npc[b].active) continue;
                                        if (npc[b].house_type != 1) continue;    // if not cave
                                        if (npc[b].house_mx != mx) continue;
                                        if (npc[b].house_mz != mz) continue;
                                        if (npc[b].house_index != e) continue;
                                        x=crh2[e].x-20.0f+(float)c*10.0f;
                                        z=crh2[e].z+40.0f;
                                        y=heightmap(mx,mz,x,z);
                                        d=add_caveman(mx,mz,x,y,z,0,0);
                                        if (d != -1)
                                            {
                                            animal[d].butchered=b;
                                            animal[d].sacrificed=1;   // 1= they're a CRH caveman
                                            }
                                        c++;
                                        }
                                    }
                                msg("You encounter cavemen from the nearby cave!");
                                if (cm[a].current_action == MOVE) adjust_bandmember_location(a);
                                setaction(a,DONOTHING);
                                gamespeed=0;
                                break;
            case HOSTILECAVE:   add_hostile_cavemen(a,0); 
                                msg("You encounter cavemen from the nearby cave!");
                                if (cm[a].current_action == MOVE) adjust_bandmember_location(a);
                                setaction(a,DONOTHING);
                                gamespeed=0;
                                hostile_CRH_encounter_underway=1;
                                hostile_fled_CRH_encounter=0;
                                break;
            case PLAYERCAVE: if (dice(10000)<=1000)  do_cave_animal_encounter(a); break;
            }
    }
    }
//    clear hut encounter flags
mx=cm[a].mx;
mz=cm[a].mz;
for (e=65000; e<num_crhs; e++)
    {
    if (! crh2[e].active) continue;
    if (crh2[e].mx != mx) continue;
    if (crh2[e].mz != mz) continue;
    if (crh2[e].encounter_checked)
        {
        if (! bandmember_near_hut(e))            // if no bandmember <= 300 from hut
            {
            remove_CRH_cavemen(3,mx,mz,0);       // 3 = hut housetype        removes friendly cavemen only!
            crh2[e].encounter_checked=0;
            }
        }
    }
//   check hut encounters
mx=cm[a].mx;
mz=cm[a].mz;
for (e=65000; e<num_crhs; e++)
    {
    if (! crh2[e].active) continue;
    if (crh2[e].mx != mx) continue;
    if (crh2[e].mz != mz) continue;
    if (crh2[e].encounter_checked) continue;
    if ( BBdist( (int)cm[a].x, (int)cm[a].z, crh2[e].x,crh2[e].z) > 75) continue;        // trigger at 75, reset at 300
    crh2[e].encounter_checked=1;
    switch ( crh2[e].owner_type )
        {
        case FRIENDLYCAVE: 
                            if (crh2[e].npc_ownerID == -1)        // no npc assigned to hut yet, so assign one
                                {
                                num=dice(10);
                                for (b=0; b<num; b++)
                                    {
                                    x=crh2[e].x-20.0f+(float)b*10.0f;
                                    z=crh2[e].z+30.0f;
                                    y=heightmap(mx,mz,x,z);
                                    d=add_caveman(mx,mz,x,y,z,0,0);
                                    if (d != -1)
                                        {
                                        animal[d].butchered=new_npc();
                                        if (b==0) crh2[e].npc_ownerID=animal[d].butchered;
                                        npc[animal[d].butchered].house_type=3;    // house type: 0=unassigned 1=cave 2=rockshelter 3=hut
                                        npc[animal[d].butchered].house_mx=mx;
                                        npc[animal[d].butchered].house_mz=mz;
                                        animal[d].sacrificed=1;   // 1= they're a CRH caveman
                                        }
                                    }
                                }
                            else
                                {
                                c=0;
                                for (b=0; b<maxnpcs; b++)
                                    {
                                    if (! npc[b].active) continue;
                                    if (npc[b].house_type != 3) continue;        // if not hut
                                    if (npc[b].house_mx != mx) continue;
                                    if (npc[b].house_mz != mz) continue;
                                    x=crh2[e].x-20.0f+(float)c*10.0f;
                                    z=crh2[e].z+30.0f;
                                    y=heightmap(mx,mz,x,z);
                                    d=add_caveman(mx,mz,x,y,z,0,0);
                                    if (d != -1)
                                        {
                                        animal[d].butchered=b;
                                        animal[d].sacrificed=1;   // 1= they're a CRH caveman
                                        }
                                    c++;
                                    }
                                }
                            msg("You encounter cavemen from the nearby hut!");
                            if (cm[a].current_action == MOVE) adjust_bandmember_location(a);
                            setaction(a,DONOTHING);
                            gamespeed=0;
                            break;
        case HOSTILECAVE:   add_hostile_cavemen(a,0); 
                            msg("You encounter cavemen from the nearby hut!");
                            if (cm[a].current_action == MOVE) adjust_bandmember_location(a);
                            setaction(a,DONOTHING);
                            gamespeed=0;
                            break;
        case PLAYERCAVE: if (dice(10000)<=1000)  do_animal_encounter(a); break;
        }
    }
//   check hut takeover
for (b=65000; b<num_crhs; b++)
    {
    if (! crh2[b].active) continue;
    if (! crh2[b].encounter_checked) continue;
    if (crh2[b].mx != cm[a].mx) continue;
    if (crh2[b].mz != cm[a].mz) continue;
    if (BBdist( (int)cm[a].x, (int)cm[a].z,  crh2[b].x, crh2[b].z ) > 50) continue;
    if (cavemen_near_hut(b)) continue;
    msg("The nearby hut has been abandoned !");
    if (cm[a].has_perm_shelter) Znewmenu("Abandon your hut and take this one over ?");
    else Znewmenu("Takeover abandoned hut ?");
    Zaddmenu("Yes");
    Zaddmenu("No");
    if (menu(2)==1)
        {
        // create perm shelter where hut is
        cm[a].has_perm_shelter=1;
        cm[a].shel_mx=cm[a].mx;
        cm[a].shel_mz=cm[a].mz;
        cm[a].shel_x=crh2[b].x;
        cm[a].shel_z=crh2[b].z;
        cm[a].perm_shel_quality=100;
        }
    crh2[b].active=0;
    pcrh2[b].active=0;
    // sort_and_index_huts("Updating huts");
index_huts2();
    cm[a].mood+=10;
    ulim(&cm[a].mood,100);
    }
//   check climbing animals
if (cm[a].location == UPATREE)
    {
    check_animal_climbing_up(a);
    }
if (second != 0)
    {
    return;
    }
//    do global minute
//    check animal encounters
b=animal_encounter_chance();
if (dice(10000) <= b)
    {
    do_animal_encounter(a);
    }
//  check caveman encounters
if (dice(10000) > 10)
    {
    return;
    }
b=caveman_encounter_chance(a);
if (dice(10000) > b)
    {
    return;
    }
do_caveman_encounter(a);
//   model intox
for (b=0; b<maxdrugs; b++)
    {
    if (! cm[a].drug[b].active) continue;
    cm[a].drug[b].age++;
    if (cm[a].drug[b].age>400)
        {
        switch(cm[a].drug[b].type)
            {
            case MUHAHBOOLEAF:          cm[a].dex--;
                                        cm[a].sleep-=50;
                                        llim(&cm[a].sleep,0);
                                        cm[a].fatigue+=50000;
                                        ulim(&cm[a].fatigue,100000);
                                        break;
            case BOOYEEBEEBERRY:        cm[a].intelligence-=2;
                                        break;
            case GAHYEEMOOGUHFLOWER:    cm[a].sleep-=10;
                                        llim(&cm[a].sleep,0);
                                        break;
            case YAHBEEBARK:            cm[a].mood-=40;
                                        llim(&cm[a].mood,0);
                                        break;
            case BAHYUHSEED:            cm[a].str-=3;
                                        cm[a].sleep-=50;
                                        llim(&cm[a].sleep,0);
                                        cm[a].fatigue+=50000;
                                        ulim(&cm[a].fatigue,100000);
                                        break;
            case GAHMUHROOT:            cm[a].speed-=2;
                                        cm[a].sleep-=50;
                                        llim(&cm[a].sleep,0);
                                        cm[a].fatigue+=50000;
                                        ulim(&cm[a].fatigue,100000);
                                        break;
            }
        cm[a].drug[b].active=0;
        }
    }
//  extinguish torches
for (b=0; b<maxobjrecs; b++)
    if (  (cm[a].stuff[b].active) && (cm[a].stuff[b].type==LITTORCH) )
        { 
        cm[a].stuff[b].qual--;
        if (cm[a].stuff[b].qual < 1) cm[a].stuff[b].active=0;
        }
if (minute%7 == 0)
    {
//   reduce hygiene due to movement
    if (cm[a].moved)
        {
        cm[a].hygiene--;
        if (cm[a].running) cm[a].hygiene--;
        if (cm[a].hygiene<0) cm[a].hygiene=0;
        }
    }
if (minute%10 == 0)
    {
//   reduce water
    cm[a].water--; 
    if (cm[a].water<0) cm[a].water=0; 
//  reduce sleep
    cm[a].sleep--; 
    if (cm[a].sleep<=0)
        {
        cm[a].sleep=0;
        if (cm[a].current_action != SLEEP)
            {
            setaction(a,SLEEP);   // sleep
            cm[a].actiondata[0]=0;
            cm[a].actiondata[1]=cm[a].sleep;    // save old sleep value for mood check when they wake up
            cm[a].actiondata[2]=100;
            if ((cm[a].dmg>0) || (cm[a].has_disease))
                {
                if (num_carried(a,MEDHERB,ANYQUALITY) > 0)
                    {
                    remove_best(a,MEDHERB);
                    cm[a].actiondata[3]=1;          // using med herbs
                    }
                else cm[a].actiondata[3]=0;         // not using med herbs
                }
            else cm[a].actiondata[3]=0;           // not using med herbs
            msg("You pass out from lack of sleep!");
            }
        }
    }
if (minute%15 == 0)
    {
//    reduce food
    cm[a].food--; 
    if (cm[a].food<0) cm[a].food=0;
//  affect mood
    if (! on_drug(a,MUHAHBOOLEAF)) 
        {
        if (cm[a].water<1) 
            {
            cm[a].mood--;
            if (cm[a].mood<0) cm[a].mood=0;
            }
        if (cm[a].food<1) 
            {
            cm[a].mood--;
            if (cm[a].mood<0) cm[a].mood=0;
            }
        if (    !on_drug(a,YAHBEEBARK) && !on_drug(a,BAHYUHSEED))
            {
            if (cm[a].dmg>cm[a].hp/2) 
                {
                cm[a].mood--;
                if (cm[a].mood<0) cm[a].mood=0;
                }
            }
        if (cm[a].has_disease) 
            {
            cm[a].mood--;
            if (cm[a].mood<0) cm[a].mood=0;
            }
        if (cm[a].hygiene<1) 
            {
            cm[a].mood--;
            if (cm[a].mood<0) cm[a].mood=0;
            }
        if (encumberance(a)>3*maxencumberance(a)/4) 
            {
            cm[a].mood--;
            if (cm[a].mood<0) cm[a].mood=0;
            }
        if (  (is_raining) &&  (!has_shelter(a))  && !on_drug(a,GAHMUHROOT) && ! on_drug(a,MUHAHBOOLEAF) )      // rain
            {
            cm[a].mood-=2;
            if (cm[a].mood<0) cm[a].mood=0;
            }
        b=cm0;
        cm0=a;
        if (  (windchill(a) < 60.0f) && (!has_shelter(a)) && (! near_fire())   && !on_drug(a,GAHMUHROOT) && !on_drug(cm0,MUHAHBOOLEAF)  )        // cold
            {
            cm[a].mood-=2;
            if (cm[a].mood<0) cm[a].mood=0;
            }
        cm0=b;
        if (temp_at(a) > 80.0f)        // heat
            {
            cm[a].mood--;
            if (cm[a].mood<0) cm[a].mood=0;
            }
        if (cm[a].social<1)            // make social affect mood
            if (dice(10000) <= 23)
                {
                cm[a].mood--;
                if (cm[a].mood<0) cm[a].mood=0;
                }
        }
    }
if (minute != 0)
    {
    return;
    }
//    do global hour
//  reduce hygiene
if (cm[a].hygiene>=1) cm[a].hygiene--;
//   model dehydration
if (cm[a].water<1) 
    {
    if (dice(100)<=12) 
        {
        cm[a].dmg++;
        cm[a].mood-=2;
        llim(&cm[a].mood,0);
        msg("You take damage from dehydration!");
        if (cm[a].dmg>=cm[a].hp)
            {
            msg("You died of dehydration!");
            kill_bandmember(a);
            }
        }
    }
//   model food spoilage
for (b=0; b<maxobjrecs; b++)
    {
    if (!cm[a].stuff[b].active) continue;
    if (object[cm[a].stuff[b].type].spoilchance==0) continue;
    if (dice(10000) >= object[cm[a].stuff[b].type].spoilchance) continue;
    cm[a].stuff[b].qual-=object[cm[a].stuff[b].type].spoilrate;
    if (cm[a].stuff[b].qual<1)
        {
        cm[a].stuff[b].active=0;
        strcpy_s(s,100,"Some of your ");
        strcat_s(s,100,object[cm[a].stuff[b].type].name);
        strcat_s(s,100," spoiled!");
        msg(s);
        }
    }
//  model exposure
if (exposed(a))  
    {
    if (dice(10000) <= 1000) 
        {
        cm[a].dmg++;
        cm[a].mood-=2;
        llim(&cm[a].mood,0);
        msg("You take damage from exposure to cold!");
        if (cm[a].dmg >= cm[a].hp)
            {
            msg("You died from exposure to cold!");
            kill_bandmember(a);
            }
        }
    }
//  model heatstroke
if  (
    (temp_at(a) > 100) &&
    ((cm[a].location==OUTSIDE) && (map[cm[a].mx][cm[a].mz].coverage!=WOODS))   &&
    ((cm[a].location==OUTSIDE) && (map[cm[a].mx][cm[a].mz].coverage!=JUNGLE))  &&
    (! has_shelter(a)) &&
    (dice(10000) <= 1000)
    )
    {
    cm[a].dmg++;
    cm[a].mood-=2;
    llim(&cm[a].mood,0);
    msg("You take damage from heatstroke!");
    if (cm[a].dmg >= cm[a].hp)
        {
        msg("You died from heatstroke!");
        kill_bandmember(a);
        }
    }
//   model drown in flood
if (watertable>6.0f)                                                                // model drowning in floods
    if(watertable>heightmap(cm[a].mx,cm[a].mz,cm[a].x,cm[a].z)+7.0f)   
        { 
        msg("You have been swept away by flood waters and drown!"); 
        kill_bandmember(a); 
        }
//   model wear n tear of clothing etc
for (b=0; b<maxobjrecs; b++)
    {
    if (!cm[a].stuff[b].active) continue;
    switch(cm[a].stuff[b].type)
        {
        case BOOTS:
        case HAT:
        case MITTENS:
        case CLOAK:
                    if ( is_raining && ! has_shelter(a) ) chance = 1000;
                    else chance = 462;
                    if (dice(10000) > chance) break;
                    cm[a].stuff[b].qual--;
                    if (cm[a].stuff[b].qual>0) break;
                    Ss("Your ");
                    Sa(object[cm[a].stuff[b].type].name);
                    Sa(" wore out!");
                    msg(S);
                    cm[a].stuff[b].active=0;
                    break;
        }
    }
//   model perm shelter raids
if (cm[a].has_perm_shelter)
    {
    if (dice(10000) <= 100)
        {
        chance = 200 - xpbonus(cm[a].xp[CAMOFLAGE]);
        rng=rng2closest_hostile_CRH(cm[a].shel_mx,cm[a].shel_mz);
        if (rng<1) rng=1;
        chance+=200/rng;
        if (dice(10000) <= chance) raid_perm_shelter(a);
        }
    }
//    moodboost nature lovers
if  (   
    (dice(10000) <= 2500)  &&   
    (has_interest(a,I_NATURE))   &&
    (   (map[cm[a].mx][cm[a].mz].coverage == WOODS) ||  (map[cm[a].mx][cm[a].mz].coverage == JUNGLE) || (map[cm[a].mx][cm[a].mz].rocks)  )
    )
    {
    cm[a].mood++; 
    ulim(&cm[a].mood,100);
    }
if (hour%2 == 0)
    {
//  model damage due to illness
    if (   (cm[a].has_disease)  &&   (dice(1000) < 100-xpbonus(cm[a].xp[HEALING]))   )
        {
        cm[a].dmg++;
        cm[a].mood-=2;
        llim(&cm[a].mood,0);
        msg("You take damage from being sick!");
        if (cm[a].dmg>=cm[a].hp)
            {
            msg("You died from an illness!");
            kill_bandmember(a);
            }
        }
    }
if (((hour>7)&&(hour<19)))
    {
//  check quest encounters
    if (  (at_friendly_CRH(a)) &&  (num_friendlies() > 0) &&  (dice(10000) <= 1000)  )   do_quest_encounter(a);
    }
if (hour != 0)
    {
    return;
    }
//    do global day
//  model starvation
if (   (cm[a].food<1)  &&  (dice(100)<=42)  )
    {
    cm[a].dmg++;
    msg("You take damage from starvation!");
    cm[a].mood-=5;
    llim(&cm[a].mood,0);
    if (cm[a].dmg>=cm[a].hp)
        {
        msg("You died of starvation!");
        kill_bandmember(a);
        }
    }
//  model getting sick
if (cm[a].hygiene<1) b=100; else b=20;    // low hygiene increases chance
b-=cm[a].con;                              // constitution decreases chance
if (exposed(a) || exposed2(a)) b+=300;   // exposure increases chance
if (dice(1000)<b)
    {
    cm[a].has_disease=1;
    msg("You've caught an illness!");
    cm[a].mood-=10;
    llim(&cm[a].mood,0);
    }
//  model background radiation - IE ageing
if (dice(10000) <= 3) 
    {
    msg("You suffer aging from background radiation!");
    cm[a].hp--;
    cm[a].mood-=10;
    llim(&cm[a].mood,0);
    if ( (cm[a].hp<1) || (cm[a].dmg>=cm[a].hp) )
        {
        msg("Background radiation (old age) killed you!");
        kill_bandmember(a);
        }
    }
//   model permanent shelter weathering
if ( cm[a].has_perm_shelter)
    {
    if (dice(10000) <= 6000)
        {
        cm[a].perm_shel_quality--;
        if (cm[a].perm_shel_quality < 1) 
            {
            cm[a].has_perm_shelter=0;
            msg("Your shelter has weathered away!");
            }
        }
    }
//  zero god relations
for (b=0; b<8; b++)
    {
    if (cm[a].god[b] > 0) cm[a].god[b]--;
    if (cm[a].god[b] < 0) cm[a].god[b]++;
    }
//   reduce social
cm[a].social-=5; 
llim(&cm[a].social,0);         // make social go down by 5 per day
//   model traps
for (b=0; b<maxtraps; b++)
    {
    if (!cm[a].trap[b].active) continue;
    cm[a].trap[b].age++;
    if (cm[a].trap[b].age>30)
        {
        cm[a].trap[b].active=0;
        continue;
        }
    switch(cm[a].trap[b].status)
        {
        case 0: c=dice(10000);
                if (c<=500) cm[a].trap[b].status=1; // caught animal
                else if (c<=1000) cm[a].trap[b].status=2;   // trap sprung, no animal caught
                break;
        case 1: cm[a].trap[b].daystrapped++;
                if (cm[a].trap[b].daystrapped > 5) cm[a].trap[b].status=2;   // trap sprung, no animal caught (rotted away)
                break;
        }
    }
//  model skill reduction
for (b=0; b<maxskilltypes; b++)
    {
    if (cm[a].xp[b] <1 ) continue;
    c=cm[a].xp[b];
    c/=100;
    if (c<1) continue;
    if (dice(10000) > 274) continue;  // works out to -1%, 10x / year on avg.
    cm[a].xp[b]-=c;
    }
}
 
 
 
 
 
 
 
compare that to BMupdate_each:
 
 
 
 
 

void BMupdate_each(int a)
{
//    do update stuff here - do global frame
BMdecrease_fatigue(a);
BMmodel_attack(a);
BMmodel_surprise(a);
BMmodel_full_fatigue(a);
run_bandmember(a);
BMmodel_raft_movement(a);
BMmodel_paddling_fatigue(a);
BMmodel_falling(a);
BMmodel_sneak_detection(a);
if (frame != 0)
    {
    return;
    }
//  do global second
BMmodel_fatigue_dueto_damage(a);
BMmodel_fatigue_dueto_encumbrance(a);
BMrun_quests(a);
BMclear_rockshelter_enccouter_flags(a);
BMcheck_rockshelter_encounters(a);
BMclear_cave_encounter_flags(a);
BMcheck_cave_encounters(a);
BMclear_hut_encouter_flags(a);
BMcheck_hut_encounters(a);
BMcheck_hut_takeover(a);
BMcheck_climbing_animals(a);
if (second != 0)
    {
    return;
    }
//    do global minute
BMcheck_animal_encounters(a);
BMcheck_caveman_encounters(a);
BMmodel_intox(a);
BMextinguish_torches(a);
if (minute%7 == 0)
    {
    BMreduce_hygiene_dueto_movement(a);
    }
if (minute%10 == 0)
    {
    BMreduce_water(a);
    BMreduce_sleep(a);
    }
if (minute%15 == 0)
    {
    BMreduce_food(a);
    BMaffect_mood(a);
    }
if (minute != 0)
    {
    return;
    }
//    do global hour
BMreduce_hygiene(a);
BMmodel_dehydration(a);
BMmodel_food_spoilage(a);
BMmodel_exposure(a);
BMmodel_heatstroke(a);
BMmodel_drown_in_flood(a);
BMmodel_wearNtear(a);
BMmodel_perm_shelter_raids(a);
BMmoodboost_nature_lover(a);
if (hour%2 == 0)
    {
    BMmodel_damage_dueto_illness(a);
    }
if (((hour>7)&&(hour<19)))
    {
    BMcheck_quest_encounters(a);
    }
if (hour != 0)
    {
    return;
    }
//    do global day
BMmodel_starvation(a);
BMmodel_getting_sick(a);
BMmodel_background_radiation(a);
BMmodel_perm_shel_weathering(a);
BMzero_god_relations(a);
BMreduce_social(a);
BMmodel_traps(a);
BMmodel_skill_reduction(a);
}
 
 
 
 
 
 
 
BMupdate_each seems much clearer to me.
 
 
Although i'm technically not optimizing here, while i have everything setup already i might as well put  timers on the two update_each functions just for fun.

Edited by Norman Barrows, 16 September 2013 - 07:16 PM.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#33 fir   Members   -  Reputation: -456

Like
-2Likes
Like

Posted 17 September 2013 - 02:30 AM

BMupdate_each seems much clearer to me.

 
Although i'm technically not optimizing here, while i have everything setup already i might as well put  timers on the two update_each functions just for fun.

 

 

Once ago I was writing some tile roguelike rpgs (eye1, eye2, eye3 - three big versions of this, not finished but improving ) and I can say my code was very like like yours, also a very big nice structures and a very big complicated stats and skills mechanics - also I discoveed the think you wrote that i 

run many such mechanics in separate way like yours - 

I consider the style of yr coding as a very fine (same way I write as I say) so I read it with nice, Its quite kool - Got not to much mood to get deeper into that you write (there is to much post on this forum and its harder to stop on something separate) but maybe i should becouse we are doing same things!



#34 phantom   Moderators   -  Reputation: 7556

Like
4Likes
Like

Posted 17 September 2013 - 05:03 AM

I'm... staggered... you go on and on about 'cache efficiency' and then post a struct like that.. that abomination... which is just going to utterly utterly screw the cache over as it's just so damned large...

And I'm willing to bet for most of your functions that data is 'cold' but being pulled in anyway.

Hell, just looking at the opening section of your 'update_each2' function, which I assume is functionally the same as the prefered 'update_each' function, makes me want to vomit.

You touch 'moved' which is over 200bytes into your structure (and 16 bytes away from the 'active' flag) and then check 'paddling' a further 172 bytes away, then go you BACK to 'current_action' which is 188 bytes behind the last touched data before using that to look up in ANOTHER array (which I assume is global so that'll probably cache miss too). At which point you go a further 20+ bytes back (without knowing the value of 'maxskilltypes' its hard to size 'xp' properly) to update that property.

Then you go on to touch 'attacking' which is 48+ bytes away again. To your credit 'attach_counter' is at least next to that one.The attack handling is probably the saner bit... when we get onto raft modeling and once again you at touching one section of the structure before wandering off to an unrelated bit to touch the 'x' and 'z' data before running the normalise function on data right at the start of the structure AND towards the end.

Paddling and falling suffers the same madness and about then I just gave up.

Frankly with this abomination NEVER EVER mention the words 'cache effiency' again because you are just playing a game of cache miss city with that setup; you data layout and access patterns are not REMOTELY sane and to even dream otherwise is just foolish.

#35 Norman Barrows   Crossbones+   -  Reputation: 2308

Like
1Likes
Like

Posted 17 September 2013 - 11:21 AM


I'm... staggered... you go on and on about 'cache efficiency' and then post a struct like that.. that abomination... which is just going to utterly utterly screw the cache over as it's just so damned large...

 

 

R.O.T.F.L.!!!!!!!     no sh*t!      <g>

 

that data structure is totally    UN-optimized !  <g>

 

too big,   poorly laid out.      screams for a C-E system.

 

like you say, its way too big for the cache, data accesses jump all the heck all over. 

 

that will most likely be addressed during the "quest for 900x accelerate time" optimizations.

 

right now, this "99 code changes" issue is more of a refactoring or re-design to simplify the code.  but while refactoring the code, i do need to keep speed in mind as well as maintainability.  thus the interest in cache friendly code (as opposed to cache friendly data).

 

despite its cache unfriendliness, its still plenty fast enough right now to run update at 450 Hz (fps) - fast enough for any normal shooter.  but not fast enough for a  vehicle sim that supports accelerated times on the order of 1024x - unless its one of those "go get a cup of coffee while you travel in accelerated time to your target" type vehicle sims.


Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#36 Norman Barrows   Crossbones+   -  Reputation: 2308

Like
0Likes
Like

Posted 17 September 2013 - 11:36 AM

i ran timing tests on BMupdate_each  (function calls)  vs BMupdate_each2   (905 lines of code)  

 

to get meaningful numbers i had to accumulate the elapsed time for BMupdate_all over 1 second. 

 

times were about a wash, with the nod going to BMupdate_each (function calls) with perhaps 5% fewer ticks on average.

 

this actually makes sense.   its doubtful that a 905 line function compiles to a pile of machine code small enough to be instruction cache friendly.

 

the function version most likely  inlines the short stuff and does calls for the larger update routines.

 

if this were an academic exercise, i'd turn warning level 5 on, and do a build, and check the god-awful long output list of inlined functions to find out.

 

so i think i'm gong to go with the function version, more maintainable, and perhaps a tad faster too.

 

now i have to figure out what to do about all the loops in the checks


Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#37 Norman Barrows   Crossbones+   -  Reputation: 2308

Like
0Likes
Like

Posted 17 September 2013 - 03:24 PM

After collapsing all the update loops together, i'm still left with approximately 50 check loops. 

 

I checked for possible C++ iterator constructs, but all were template based with RTT check code overhead, and therefore a slight performance hit. since the code only runs at ~450Hz currently, and the ideal target is an undoable ~13.5KHz, similar constructs without the RTToverhead would be preferable.  

 

to that end, i took a cue from ApochPiQ, and created a generic foreach_aliveBM()  iterator routine which takes a function as a parameter.  

 

that turns code like this....

 

 
 
// reduce fatigue due to movement
int a
for (a=0; a<maxcavemen; a++)
     {
     if (! cm[a].active) continue;
     if (! cm[a].alive) continue;
     if (cm[a].moved)
          {
          cm[a].fatigue--;
          if (cm[a].speed==SPRINT) cm[a].fatigue-=2; else cm[a].fatigue--;
          }
    }
 

 

 

 

into something like this......

 

 

 
 
 
 
// reduce fatigue due to movement
void update_fatigue(int a)
{
if (cm[a].moved) 
    {
    cm[a].fatigue--;
    if (cm[a].speed==SPRINT) cm[a].fatigue-=2; else cm[a].fatigue--;
    }
}
 
 
 
foreach_aliveBM(update_fatigue);
 
 

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#38 Norman Barrows   Crossbones+   -  Reputation: 2308

Like
0Likes
Like

Posted 17 September 2013 - 03:35 PM

here's the foreach generic iterator code....

 
typedef void functiontype2(int);
 
 

void foreach_aliveBM(functiontype2 *f)
{
int a;
for (a=0; a<num_bandmembers-1; a++)   if (cm[a].alive) f(a);
}
 
 
void foreach_activeBM(functiontype2 *f)
{
int a;
for (a=0; a<num_bandmembers-1; a++)  f(a);
}
 
 

Edited by Norman Barrows, 17 September 2013 - 03:40 PM.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#39 Norman Barrows   Crossbones+   -  Reputation: 2308

Like
1Likes
Like

Posted 17 September 2013 - 03:42 PM

post got clipped cliped again!

 

 

continuing form last post.....

 

 

and here's the new version of BMupdate_all that uses the foreach routine....

 

(still have to call move_rafts first... <g>  )

 
 
//    must call move_rafts first!
void BMupdate_all()
{
foreach_aliveBM(BMupdate_each);
}
 
 

Edited by Norman Barrows, 17 September 2013 - 03:43 PM.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

 

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

 

 


#40 FLeBlanc   Crossbones+   -  Reputation: 3117

Like
0Likes
Like

Posted 17 September 2013 - 03:58 PM

I'm just curious what the point of all this posting is. Are you asking for help with your (godawful spaghetti) code, or what? The running commentary here just seems sort of bizarre, and probably more suited for a journal or blog of some sort than the Game Programming forum.






Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS