• entries
    44
  • comments
    44
  • views
    58275

About this blog

About the game Caveman

Entries in this blog

Norman Barrows
World coordinate system based cloud particle system done


well that didn't take long!

Caveman uses the following world coordinate system:

The game world map is divided up into 5x5 mile map squares. Each map square has its own d3d coordinate system. So a location in the world is speccified by map x and z, AND by a d3d x,y,z in that map square: IE mx,mz,x,y,z.

all i had to do was add an mx,mz to a cloud struct.

init them to the players mx,mz in init_cloud2

made draw_cloud3 use them instead of the player's mx,mz to calculate camera relative coordinates.

added a wrap_around_cloud function to wrap a cloud around to the other side of the cloud box when it moves outside the cloud box, or the cloud box moves beyond it.

added a movecloud function to move a cloud based on windspeed and direction from the weather engine

made sort_clouds (for alpha blending) use world cloud coordinates converted to camera relative coordinates for sorting

added a remove_last_cloud function to remove the last active cloud from the array of cloud structs. The array of cloud structs is statically allocated, as the max number of particles (currently 400) is known at compile time. each cloud struct has an "active" boolean (implemented as int, 0=false, 1=true). when a cloud is added, its "active" variable is set to 1. when its removed, its "active" variable is set to 0. only active clouds are rendered and updated.

added an update _clouds function that adds or removes clouds based on changes in cloud cover info provided by the weather engine, and then calls update_cloud for each active cloud.

added a wrap_around_clouds function that calls wrap _around_cloud on each active cloud. the "cloud box" is the area around the camera that active clouds are in. the cloud box is currently set to a bounding box radius of 2000 units around the camera, and altitudes from 500 to 700 units. since the cloud box is based on the camera's location, the camera must be set before making clouds outside the cloud box wrap around. this tripped me up for a moment. i was handling wrap around once after player/camera/cloud box movement and cloud movement had both been done. but when you changed map squares, it seemed to draw the clouds at their old position (IE 5 miles away) for one frame. doing wrap around after cloud box movement and after cloud movement didn't fix things. the reason is because the player had moved to the next map square, but render hadn't been called, yet, so the camera hadn't been moved to the next map square yet! the solution was to call wrap around once on all clouds just before drawing, at which point the camera has already been set to the new map square.

so now the clouds come and go as you move, and come and go with the winds, and don't change drawing order when you change map squares, and increase and decrease in number with changes in cloud cover. now i need to add grey and red cloud textures and make it use them when appropriate. The original version supported blue, grey, and red skies, and white, grey, and red clouds. Right now the new version only supports blue skies and white clouds.

I think a lot of why it was so quick and easy had to do with the following:

1. CScript. all the new code was done in CScript which allowed the quick coding of the simple routines required.

2. Existing functions in the game. things like normalize_location and camera_relative_coords. being able to copy paste edit x+=spd*cos(yr) etc from generic_move to update_cloud. i can never remember if its x+=sin or x+=cos. wait. just thought about it for a sec, its x+=sin(yr). but usually i'm lazy and just find the formula somewhere in the code like generic_move (moves the player any of 4 directions), move_animal (moves a non-player entity), or move_missile (moves a projectile), and copy and edit it - less typing!

3. the fact that i've been writing particle systems for games for 25 years now. yeah - i know, no fair!

CScript really is nice, i should have been using it all along. i can code new stuff and mod old stuff in a flash. and it only adds 2 seconds to the build time for 71,000+ lines of source code, and that includes the time to minimize visual studio 2012, launch CScript, click to quit when it finishes translating, maximize VS 2012 again, and hit F7 to build. I had it setup as a pre-build step, but since you edit the CScript .cs input file and not the .cpp output file generated by CScipt, VS 2012 thinks the build is up to date since the .cpp file hasn't changed (yet). this means you have to do a rebuild to trigger the CScript pre-build action, or make a change to the current .cpp file. so i went back to a shortcut on the desktop to launch CScript. i also tried it as a tool button, but you can't make it project specific to pass project specific source file names. There's probably some way to do it, but the desktop shortcut with the file name as a parameter works fine.
Norman Barrows
Work on skinned meshes continues

I added the ability to draw weapon-in-hand to the skinned mesh test routine. It can toggle between weapon types, and uses the game's built-in matrix editor to position the weapon with respect to the hand bone. The test routine is used to determine the fixup transform for a weapon, which is then added to the database info for that weapon type.

It now draws all 65 or so different types of weapons in a skinned mesh female's hand.

A fixup transform for male meshes is next. Male and female meshes share almost identical skeletons, so just a single fixup for male vs female skinned mesh is required to make all the weapons draw in a male skinned mesh hand as well - IE move everything down and right a bit to account for the difference in the male and female hand meshes. These fixup transforms are roughly the equivalent of positioning a weapon bone for each skinned mesh in the modeling software, and transforming all weapon models in the modeling software so their grip point is at the origin, then drawing the weapon at the weapon bone with no transform. Unlike your typical shooter, not every weapon in Caveman has a uniform type and size of grip, such as a pistol grip or sword handle. so tweaking on a per weapon basis is really required to get the best possible fit.

Then comes the remaining attack animations. One hand attack is already done. spear thrust, bow attack, and a few others still need to be made. It looks like I can even share animations between male and female skeletons. It also looks like I need to clean up the skin weights a bit on the male meshes - the upper arm influences the ribs area. I thought it was from using the female attack animation, but its just a weight tweak issue, not an animation sharing issue.

I'll need to make a version of the skinned mesh textures for use with the existing 1st person attack animations - a simple copy/paste type operation in paint.net or gimp.

I also need to make some improvements to the startup resolution autodetect code. Right now its selects the resolution with the highest horizontal value that supports A8R8G8B8, and assumes 24bit depth buffer support is available at that resolution - which may not be a safe assumption in rare cases. I've only had two people report such issues.

And then its time to release beta 21. Beta 20 has already expired, so at the moment there is no beta available for download. Beta 21 should be ready in a couple of weeks. In order to get beta 21 out sooner, it will not draw armor or equipment, and won't have action animations. those will all be included in beta 22, which will probably take 2-3 weeks from the time beta 21 is released.

And then comes skinned mesh animals....

i'd better get back to work!
Norman Barrows
The Building of Caveman - Part 4

Z3D - The low level generic game development library




So far i had a wrapper for the dx9 fixed function pipeline, and a
render queue and state manager desigend to feed data to the pipeline
in optimal order for fastest performance. The render queue allows
the game to make drawing callis in a more of less arbitrary order.

But it takes much more than a render queue and state manager to
build a game. additional componets required included:

* fonts
i went with d3dx fonts at first. later, i switched
to a custom font using faster d3dx sprites.

* materials
d3d lighting equations are complex and powerful.
i decided to control lighting primarily via materials to simplify things.
the game uses a database of about 8 materials. everything from dirt to
emissive beacon. Some materials such as plant specular and clouds vary
depending on time of day. A fair amount of time was spent tweaking the
lights and materials for the best lighting behavior possible with the
limited capabilites of dx9 fixed function.
A side benefit of controlling lighting
through materials is the ability to adjust brightness by simply scaling
material values. This avoids the "washed out" look of using gamma to
adjust brightness.

* textures
i created a textures database. a simple array of d3dx9 texture pointers.
it includes routines for creating, loading, and saving textures.
It supports solid and alpha tested textures with or without mipmaps,
as well as alpha blended textures with mipmaps.
textures are refereced via their array index (Texture ID).

* meshes
again, a simple database. d3dxmesh pointers at first, later replaced by
a struct with a vb, ib, numverts, and numtris. references are via
index # (Mesh ID).

* multi-mesh models
since the game uses one texture per mesh for speed, two textures means
two separate meshes. so a rigid body modeling system was created.
It was also required for the rigd body charater animation system.
Caveman uses rigid body animation instead of skinned meshes.
This allows it to draw about five times as many characters on
screen at once.
So a models database was required to hold the model data.
its an array of structs, obe for each type of model.
a struct contains info like the number of limbs, and the mesh, texture,
scale, location, parent, etc, of each limb.
As with all the content databases in the game, reference is via
array index # (model #).

* animations
animations are keyframe in style, and manipulate the limbs of
a multi-mesh model. the game has a database of animations (array
of structs). each struct contains info like number of keyframes,
limb rotations for each keyframe, number of frames between keyframes,
etc.

* animation players.
an animation player takes an animation, and a model, and an animation
frame counter, and tweens the model for drawing. animation players are
assigned to bandmemebrs, animals, and npc's when they start an animation.
the game contains an array of 200
animation players - an "animation manager".
a animation player is just a struct with info like an "active" field,
animation #, model #, and framecounter.

* sprites
d3dx sprites, with some wrapper routines to provide an api like:
drawsprite x,y, x scale, y scale
where x,y is the upper left corner, and the scale values are
0.0 through 1.0 inclusive. sprites use the textures database.

* gui components
a popup menu, and a text entry dialog box. the popup menu can
double as a message dialog box, just make the last option "ok",
and repeat until they select "ok". everything else is custom
screens done with sprites and fonts. thankfully there are very
few custom screen required - less than a dozen. menu() and
getstring() handle all the rest. There is also a text file
viewer for help. the menu system uses an array of strings.
the game calls Znewmenu(title_string) to begin assigning text
to a menu to be displayed. it then calls Zaddmenu(option_text)
to add text for menu options it then calls pick=menu(num_options)
to get a menu pick (option #).

* lights
Caveman tries to do simple to implement and fast yet realitic
lighting within the limitations of DX9 fixed function. It uses
a single directional light for sunlight and moonlight. point
lights with a small radius are used for fires and torches. These
are the only lights used. sun and moon light are varied based
on time of day and cloud cover. Lighting is primarily controlled
via materials, with an overall lightness_factor() being the only
variation in actual light settings, and then only for the sun/moon
light. Light settings for rendering scenes were determined first,
then materials were adjusted to get the desired behavior, with
new materails added as new behaviors were required. Light settings
are heavy on the diffuse, and very light on the ambient for a more
realistic look.


many other low level genric routines are reqiured for games:
math functions
file i/o
high resolution timers
keyboard input
mouse input
etc.

in the past, i've collected such routines into a generic game
development library that i've both used in-house and sold publicly
(passes the dog food test! ).

so i proceeded to round out my graphics code with the additional
generic low level stuff required. The result is the latest
incarnation of Rockland Software Productions game development
library. This version is called the Z game library.

the Z3D module is the main module, containing the graphics and
generic game code. There's also an audio module, and a
modeler / animation editor module. The are also experimental
modules for fonts, gui components, a "game engine", an entities
list with AI routines, and a directx 11 version of the Z library.

here's the API for the Z3D module:
// Z3D.h #ifndef z3d_h#define z3d_h #include #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers#include #include #include #include //####################### PUBLIC GLOBAL DEFINITIONS ################################## #define pi D3DX_PI //################## PUBLIC GLOBAL VARIABLES ##################################################### extern HWND Zprogram_window_handle; extern int Zalphatestlvl; extern int mousewheel; // d3d font objectsextern ID3DXFont *pFont;extern ID3DXFont *pFont2;extern ID3DXFont *pFont3; // d3d sprite objextern ID3DXSprite *Zsprite; extern D3DPRESENT_PARAMETERS Zparams;extern LARGE_INTEGER Ztimer_freq; extern int Ztrace[20]; extern DWORD Znum_verts,Znum_primitives; extern int Zcurrentmesh,Zcurrenttex; extern int old_mousex,old_mousey; //######################### MATERIALS ##################################### #define ZMAXMATERIALS 10 extern D3DMATERIAL9 Zmaterial[ZMAXMATERIALS]; void Zsetmaterial(int materialID); void Zinit_materials(); void Zload_materials(); // load materials from materials.dat file //############################# MENU STRINGS ################################### #define MaxMenuStrings 50#define MenuStringLength 100 extern char Zmenu[MaxMenuStrings][MenuStringLength]; // menu stringsextern int LastMenuOption; void Zgetstring(char *s,char *s2);int Zdomenu(int mousetex);void Zclearmenu();void Zaddmenu(char *s);void Znewmenu(char *s); // clear + addint Zmenu_char_width(int i); // returns menu width in chars //############################# SPRITES ###################################### extern ID3DXSprite *Zsprite; // d3d sprite obj void Zbeginsprite();void Zendsprite();void Zdrawsprite(int texID,int x,int y,float sx,float sy); // x,y are UL corner of spritevoid Zdrawsprite2(int texID,int x,int y,float sx,float sy,float rz); // draw a rotated sprite. x,y are screen coords of the center.void Zflushsprites(); //########################## tex DB ############################################ #define Zmaxtextures 400 struct Ztexrec{char name[100];LPDIRECT3DTEXTURE9 tex;}; extern Ztexrec Ztex[Zmaxtextures];extern int numtextures; void Zloadtex(char *s);void Zloadbitmap(char *s);void Zloadsprite(char *s);void Zload_dynamic_bitmap(char *s);void Zloadspritetex(char *s); // alpha transparencyvoid Zloadspritetex2(char *s); // alpha transparency, 5 mip lvls in dds file.void Zloadspritetex3(char *s); // colorkey transparencyvoid Zunload_last_bitmap(); void Zload_textures(); //########################### mesh DB ########################################### #define maxmeshes 300 struct Zmesh{IDirect3DVertexBuffer9 *vb;IDirect3DIndexBuffer9 *ib;int numverts,numfaces;}; // meshDB recordstruct meshrec{char name[100];//LPD3DXMESH mesh;Zmesh mesh2;}; // mesh DBextern meshrec mesh[maxmeshes]; void Zloadmesh(char *s,int dynamic); // dynamic = 0 or 1. extern int DBnummeshes; void Zload_meshes(); void optimize_meshes(); //######################### DRAWLIST ################################################# struct Zdrawinfo{int type, // 0=mesh, 1=model, 2=2d billboard, 3=3d billboardmeshID, // for models: modelID texID, // for models: aniID alphatest,cull,clamp,materialID,rad,cliprng,data[5];float sx,sy,sz,x,y,z,rx,ry,rz,range; // range is rng to camera. not currently used.D3DXMATRIX mWorld;}; extern int Znumdrawrecs;//extern int alphatest_numdrawrecs; void Zcleardrawlist(); void Zcdi(Zdrawinfo *a); // "ZClearDrawInfo". memset's a Zdrawinfo struct to 0. void Zdraw(Zdrawinfo *info);void Zdraw2(Zdrawinfo *info);void Zd(Zdrawinfo *b); // draw mesh, model, 2d billbaord, or 3d billboard void Zdrawlist(); void Zpresent();int Zshowscene(); // 1=success 0=failure //############################# models ################################################### #define maxlimbs 20 // limb recordstruct limbrec{int parent,meshID,texID,alphatest; // parent = index of parent limb. -1=no parent.float x,y,z,xr,yr,zr,sx,sy,sz; // location of limbs origin (in parent's coords). current orientation of limb.float xr0,yr0,zr0; // "at rest" orientation of the limbchar name[20];}; // model recordstruct modelrec{int numlimbs;limbrec limb[maxlimbs];char name[20];}; void Zdrawmodel(Zdrawinfo *info); // meshID=modelID. uses x,y,z,xr,yr,zr.void Zdrawmodel2(Zdrawinfo *info); // meshID=modelID. uses mWorld. //################################### models datatbase ########################################## #define maxmodels 200extern modelrec model[maxmodels];extern int nummodels; void loadmodel(char *s);void savemodel(int modelID,char *s); void Zload_models(); // load models from models.dat file //####################### animations ################################################ #define maxkeyframes 20 // a 3d vector (position, orientation, etc)struct xyzrec{float x,y,z;}; // keyframe recordstruct keyframerec{int numframes,numlimbs;xyzrec rotation[maxlimbs];}; // animation recordstruct anirec{int numkeyframes;keyframerec keyframe[maxkeyframes];}; int startani(int aniID,int modelID);void stopani(int playerID);int set_animation_frame(int playerID); // animation player recordstruct aniplayerrec{int active,aniID,modelID,curkeyframe,curframe;}; #define maxaniplayers 200 // animation playerextern aniplayerrec aniplayer[maxaniplayers]; //############################ animations DB #################################### #define maxanimations 200 extern anirec animation[maxanimations];extern int numanimations; void loadani(char *s);void saveani(int aniID,char *s); void Zload_anis(); // load anis from anis.dat file //########################## MATRIX MANIPULATOR ################################ extern D3DXMATRIX Mmat;void Mstart();void Mrotate(int axis,int degrees);void MrotateRADS(int axis,float radians);void Mmove(float x,float y,float z);void Mscale(float x,float y,float z);void Mtrans(float *x,float *y,float *z); // transform point x,y,z by matrix Mmat //#################### STRING MANIPULATOR ################################# extern char S[1000];void Ss(char *s2);void Sa(char *s2); // string add intvoid Sai(int i); // string add floatvoid Saf(float f); //###################### GRAPHICS ENGINE ######################################### const DWORD ZFVF=(D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2(0) ); // D3DFVF_DIFFUSE | D3DFVF_SPECULAR | /*struct Zvertexrec{float x,y,z;float nx,ny,nz;float tu,tv; DWORD diffuse,specular;};*/ struct Zvertexrec{D3DVECTOR position,normal;//D3DCOLOR diffuse,specular;float tu,tv;}; extern IDirect3DDevice9 *Zd3d_device_ptr; extern D3DXMATRIX ZmView; // view matrixextern D3DXMATRIX Zprojection_matrix; // projection matrixextern float Zcamx,Zcamy,Zcamz,Zcamxr,Zcamyr,Zcamzr; // camera position int Zinit(HINSTANCE program_instance_handle,int width,int height,char *progname,float nearplane,int farplane); // ,int zbuf24bit);void Zshutdown();void Zsetlite(DWORD liteID,float x,float y,float z,float brightness);void Zsetcam(float x,float y,float z,float rx,float ry,float rz);void Zsetcam2(float x,float y,float z,D3DXMATRIX *m); // m is rot matrixvoid Zclearscreen(int r,int g,int b);void Zclearscreen2(); // clears screen to z game engine default color (blacklight)void Zclearzbuf();void Zbeginscene();void Zambient(int r,int g,int b);void Zlighting(int onoff);void Zalphablend(int onoff);void zbuftest(int onoff);void zbufwrite(int onoff);void Zcull(int onoff);void Znormalize(int onoff);void Zlite(DWORD liteID,int onoff);void Zalphatest(int onoff);void Zmipmaps(int onoff);void Zclamp(int onoff);//void Zanisotropic(int onoff);void Zminmagfilter(int filter_type); // filter_type: 0=point, 1=linear, 2=anisotropicvoid Zspecular(int onoff); int Zsuperclip(int x,int y,int z,int rad,int cliprng); void Zset_clip_planes(float nearplane,int farplane); void Zcalc_frustum_planes(); // must set proj and view mats first! int Zsuperclip2(int x, int y, int z, int rad, int cliprng); // must call Zcalc_frustum_planes first! int Zarea_in_frustum(int x1,int z1,int x2,int z2); // must call Zcalc_frustum_planes first! void Zsetmesh(int meshID); void Zcreate_mesh(int vbsize,int ibsize); void Zsettex(int texID); void Zdrawimmediate(Zdrawinfo *a); void Zdrawimmediate2(Zdrawinfo *a); // uses Zdrawinfo's mWorld mat void Zcalc_frustum_planes2(); // rotation only //############################ TEXT ######################################## void Ztextcolor(int r,int g,int b);void Ztext(int x,int y,char *s);void Ztext3d(int x,int y,char *s);void Zsetfont(int font); // 0=comic ms sans 1= ariel 2=times new romanint textwidth(char *s); // returns width of string s in pixels. uses current font setting. //################################ MOUSE ######################################### void Zgetmouse(int *x,int *y,int *b);void Zputmouse(int x,int y);void nobutton();void pause(); //########################## WINDOWS MESSAGE PROCESSOR ############################ void Zdomessages(); //########################### GENERIC GUI COMPONENTS ################################### void Zmsg(char *s,char *title);void Zmsg2(char *s); //############################ KEYBOARD ########################################## int Zkeypressed(int vkey); //####################### STRING CONVERSION ROUTINES ############################# int s2i(char *s);void i2s(int i, char *s);void f2s(float f,char *s);float s2f(char *s); //############################ DICE ###################################### int dice(int a);void initdice(); //####################### TIMERS ######################################## void Zstarttimer(int a);int Zelapsedtime(int a);int Zelapsedticks(int a); //####################### FILEIO ########################################## // general int filefound(char *s);int filesize(char *s);time_t timestamp(char *s); // returns file create time (UTC format: secs since 1970). returns -1 if file not found.void closefile(FILE *f); // text file i/o FILE* infile(char *s); FILE* outfile(char *s); int readfile(FILE *f,char *s); // returns: 0=fail 1=successint readfileint(FILE *f);float readfilefloat(FILE *f); void writefile(FILE *f,char *s);void writefileint(FILE *f,int i);void writefilefloat(FILE *f,float i); // binary file i/o FILE* infilebin(char *s);FILE* appendfilebin(char *s); FILE* outfilebin(char *s); int readfilebin(FILE *f,unsigned char *buf,int numbytes); // returns numbytes readint readFBint(FILE *f);float readFBfloat(FILE *f); void writefilebin(FILE *f,unsigned char *buf,int numbytes);void writeFBint(FILE *f,int a);void writeFBfloat(FILE *f,float a); //####################### GENERIC MATH ROUTINES ############################# int isin(int x,int y,int x1,int y1,int x2,int y2);int dist(int x1,int z1,int x2,int z2); // diamond bounding box distint dist3d(int x1,int y1,int z1,int x2,int y2,int z2);int BBdist(int x1,int z1,int x2,int z2); // sq bounding box distfloat deg2rad(float deg);float rad2deg(float rad);int heading(int x1,int z1,int x2,int z2);int relheading(int x1,int z1,int h1,int x2,int z2);void llim(int *a,int b);void ulim(int *a,int b);void limit(int *a,int b,int c); // 0 degrees = up. 90 degrees = forward. 180 degrees = down. int angle(int x1,int y1,int z1,int x2,int y2,int z2);int rng2D(int x1,int z1,int x2,int z2);int BBdist3d(int x1,int y1,int z1,int x2,int y2,int z2); // 3d bounding box distancevoid normangl(float *a); // normalize angle to 0 to 2 pifloat rng3d(float x1,float y1,float z1,float x2,float y2,float z2); //################################ TEXT COLORS ############################### void redtext();void greentext();void bluetext();void yellowtext();void magentatext();void cyantext();void whitetext();void blacktext();void greytext(); // ################################### mouse aimed camera ################################### void Zresetmouse(); extern float cam_xr;extern float cam_yr; int Zmouse_aim_camera(); // returns mouse button pressed: 0 none, 1 LB, 2 RB, etc. // load meshes and textures using a list of filenames stored on disk in a text file //################################## SCREEN DUMP ############################################## void Zscreendump(); //############################ TARGET LIST ######################################################### // #################################################### TARGET LIST DATA STRUCTURES ##################################################### #define stepsize 0.1f struct ttyperec // a target type record{float maxspd,turnrate;int ai,rad,hp,mass,canfly,userID,value,animated,usefuel,maxfuel;Zdrawinfo d;}; struct tgtrec{float x,y,z,xr,yr,spd;int type,dp,owner,fuel,alive,active,userID,aniplayerID,dontdraw;}; #define maxttypes 50#define maxtgts 200 extern ttyperec ttype[maxttypes]; // target types database extern tgtrec tgt[maxtgts]; // targets database extern int numtgts; // index of last active tgt + 1. // max possible number of active tgts at the moment. // search tgt list using: for a=0 a


Note that Caveman uses its own custom target list, AI,
skybox, mouse aimed camera, fonts, screen dump, FPS meter,
ground drawing code, and buttons. The genric GUI components
are only used for error messages during startup before going
fullscreen 3d.


part 3:
https://www.gamedev.net/blog/1730/entry-2259520-the-building-of-caveman-part-3-getting-a-handle-on-directx/
Norman Barrows
The building of Caveman - part 6 - data structures

When i first got into programming as my major, my biggest fear was that I'd end up as a database programmer.
Turns out I became a gamedev. But i still have to do database programming. Sure, games are interactive
multimedia entertainment simulations, but underneath all that is - you guessed it - relational databases!
It turns out that a complex game can have a rather complex relational database system behind it.

Terminology:
database or list: a static array of structs, usually a one dimensional array.
record: a struct in an array
field: a variable in a struct.

In the game, most databases and lists are implemented as static arrays of structs. structs are referenced
via their array index. Maximum sizes required are known ahead of time, which allows the use of fast simple
static data structures. Many of the databases are relational, with some field (variable) in a record
(struct) being the array index of some other record (struct) in some other database (array of structs).
Some of the databases are nested, with one field in a record being an entire list. IE a variable in a
struct might be a whole array of structs. For example, each band member struct has a member varable which
is a list of active traps, implemented as an array of structs.

These are the data structures used by the game:

Asset databases:
First off, the generic databases for assets:
1. meshes - a VB and IB, numtris, and numverts.
2. textures - a d3d texture of some type
3. models - models simply refer to meshes and textures.
4. animations - a list of keyframes with limb orientations
5. animation players - a playback counter, plus model and animation references.
Caveman follows a mantra of one texture per mesh for speed. Models are made up of multiple meshes. An
entry in the models database references meshes in the mesh database, and textures in the texture database.
An entry in the animation players database references a model from the models database, and an animation
from the animations database. All asset databases are part of the generic Z game library used by Caveman.


Type databases:
1. actions - prerequisites, time required, chance of success, etc. prerequisites can include references to
skills and object types.
2. skills - prerequisites to learn, time required to learn, chance of learning success, etc. prerequisites
can include references to skills and object types.
3. object types - prerequisites to craft, weight, damage, cost, time to craft, chance to craft, etc.
prerequisites can include references to skills and object types.
4. animal types - hit points, turn rate, speed, etc. IE non-variable stats. animal types include a reference
to the models database, and references to the animations database.






single instance lists:
1. active bandmembers - an entity list for PCs. the player can tab between PCs at any time. Other PCs are
controlled by AI. each active bandmember has an inventory list, a list of active traps, a list of active
quests, and a list of active intoxicants. a bandmeber record has references to the following databases:
models (for body), meshes (for face and hair), textures (for skintone), and animations.
2. active animals - all NPC cavamen and animals (every non-PC entity). animals which are of type 2
(homo sapien) refer to an entry in the NPCs database where additional NPC specific data is stored.
animal records also refer to the animal types database, and the bandmembers list (for owners of pets).
3. active missiles - arrows, throwing rocks, etc. Smoke from fires may be implemented as a type of missile.
missile records refer to either the bandmember list or the active animals list (for owner), and also
refers to the object types database.
4. world objects - temporary shelters, storage pits, rafts, landmarks, dropped objects, etc. Basically
anything man-made except for huts. structs in this array reference the object types database.
5. active storage pits - a list of storage pits created by the player. refers to the world objects database
(a storage pit is a world object). includes an inventory list for each pit.
6. active rafts list - a list of active rafts. references the world objects database (a raft is a world
object).
7. NPCs - non bandmember cavemen. contains info on NPCs encountered by the player. references the
model, mesh, and texture databases. includes an inventory list for each NPC. NPCs at a CRH reference the
CRH list.
8. CRHs (caves, rockshelters, and huts) - a list of all the caves, rockshelters, and huts in the game -
83,000 of them! each entry has an inventory list (paged from disk). CRHs occupied by animals refer to
the animal types database. There is also a player's version of the CRH list, that only contains info
about CRHs the player has discovered.
9. player cave list - a list of those CRHs controlled by the player. references the CRH list. basically
an index of player controlled CRHs for use with the CRH database.



single instance matrixes (2d arrays of ints):
1. numconvos - tracks the number of conversations this day between band members and NPCs
2. relations - tracks relations amongst bandmembers, and between bandmembers and NPCs.



multiple instance lists:
1. inventory lists - list of objects carried, stored, or for trade. entires in the list refer to the
object types database. each bandmember, storage pit, NPC, and cave/rockshelter/hut occupied by
friendlies has an inventory list as one variable in their struct declarations.
2. Trap lists - a list of the status of active traps set by a band member. refers to the world objects
list (the trap is a world object). each band memeber record includes a trap list field (variable).
3. Active quest lists - a list of active quests. references the animal types database. each band member
struct has an active quest list.
4. Active intoxicants lists - a list of intoxicants currently affecting a band member. references the
object types database. each bandmember struct has an active intoxicants list.



handy structs used in the game:
i find that sometimes its handy to have a sturct to pass info around.
1. location - the world map is a 500x500 grid of map squares. each map square is 5 miles across.
a location is specifed by its map square indices (mx,mz) and by its d3d location in that map
square (x,y,z). a scale of 1 d3d unit = 1 foot is used. a location struct is a handy way to store
both the map sqaure and location of an object.
2. requirementsrec - a list of parts, tools, and skills required to do something. used by skills,
object types, and actions.
3. Zdrawinfo - all the info to draw a mesh, static model, animated model, 2d billboard, or 3d billboard.
used everyhwere to pass drawing info around.
4. objectrec2 - an inventory list entry: active, object type, quantity, quality, location. Used for
inventory and world object lists.





world map data structures:
1. the world map - a 2d array of structs. the world map is 500x500 map squares in size. a map square is
5 miles across. a location is specified by its map sqaure indices (mx,mz), and its d3d location in the
map square (x,y,z). a scale of 1 d3d unit = 1 foot is used. a maps sqaure struct contains info for
elevation, vegetation coverage, water, resources, etc. there is also a player's version of the world map
that only contians data for explored areas.
2. the local map - basically a 2d array of ints, used as booleans, explored/unexplored. This is used as
a "bitmap mask" to cover over unexplored sections of a map square when viewing the local map.
3. cavern maps - a 2d array of ints. values indicate walls, open space, encouter points, exits, etc.
4. plant maps - i needed a way to tell it where to draw plants, rocks, trees, etc. generating a bunch of
level maps was out of the question - too big for memory. what i came up with was what i call a "pattern
map". its a sparse matrix list of objects (trees, rocks, plants, etc). when drawing the scene, this
pattern map is tiled across the ground. Maps are large enough that no repetition is within visible range.
the game uses 4 such pattern maps - each one different - to place rocks, trees, berry bushes, etc.
4. wood grass map - another pattern map, for undergrowth in woods.
5. ground texture map - a 2d array of ints. caveman uses ground texture tile sets of 4 textures each. the
ground texture map contains random values 0 through 3, indicating which tile in the set to use for
texturing a ground quad. Agian, this "pattern map" is large enough so repetition is not within visible
range.
6. generic pattern map - as development progressed, i found myself needing a pattern map from time to time.
so i decided to create a generic one that i could write functions for that would return whatever i needed.
the geneic pattern map is a 100x100 array of random ints 0 through 100. functions are then written to
return values as needed. example: return the scale of a plant at x,z: map x,z to 0-99,0-99, and return
some permutation of the value in the generic pattern map at that location. Uses of the generic pattern
map include control of model, texture, scale, rotation, and offset of tall grass plants, and as the
basis for a double interpolated height map for canyons.




terrain chunks:
originally, the game drew the scene by brute force, drawing the ground one quad at a time, or with a dynamic
buffer, and iterating thorough the various pattern maps drawing tress and rocks etc, all the while checking
for collisions with man made objects (dont draw plants inside a hut!). Scene composition simply took too
long this way. So i stared at the lowest level and let that define how the code should work and how the
data should be organized. basically, what directx wants is an ordered list of renderables, sorted on
texture, then mesh, then near to far (for non-alpha). instead of generating the list of renderables each
frame, i decided to divide the terrain into chunks 300x300 d3d units (feet) in size. a chunk consists of
an unordered list of all renderables in the 300x300 area (in zdrawinfo structs), and a 3D index: a list of
textures, and for each texture, a list of meshes, and for each mesh, a list of instances which are indexes
into the list of renderables for the chunk. chuncks are genrated as needed and then stored in a cache with
LRU discard. to draw a chunk, the index is used to traverse the list of renderables in correct drawing order.
for each renderable, a frustum cull check is preformed, and if it passes, its added to the render queue.





the render queue:
the 3d indexed renerables list from chunks worked out so well that i redesigned the render queue in the Z
game library to use a 3d index. originally it was a simple list that got sorted just before drawing. Now,
as a renderable is added to the queue, its also added to the 3d index, in what is essentially a bucket
sort type of thing.




And that's pretty much it for the data structures. If i had to do it all over gain, i'd use more of a
composition type approach, and give animals and bandmebers a location struct for example. that way i
could write one piece of code to work with a location instead of one to work with bandmembers and one
for animals. As i go along, i try to do this with new code i write.

Note that almost everything is stored in ram. local maps use a cache and are paged to and from disk as
needed. Cavern maps are generated as needed, saved when you leave a cavern, and loaded when you
re-enter a cavern.


Part 5:
https://www.gamedev.net/blog/1730/entry-2260302-the-building-of-caveman-part-5-general-code-organization/
Norman Barrows
The building of Caveman - Part 5 - General code organization


This is the general code organization used in Caveman:


void main:
----------------
initprog
runprog
endprog





initprog
------------------
init z3d game library - creates window, goes into 3d mode, creates fonts, inits audio, random number generator, timers, etc.
rockland animation - show company logo animation
load all assests - show loading screen. load: meshes, textures, models, animations, wavs, etc.
show title screen



runprog
-------------------
show main menu:
continue - load last saved game, rungame.
tutorial - init tutorial game, rungame.
new game - init new game, rungame.
load game - load saved game, rungame.
help - show help
tools - show modding tools menu
quit - quit caveman


endprog
-------------------------
unload all assets
shutdown d3d, audio, etc




Rungame is the main game loop, the real guts of the game. The game uses a fixed time step. The framerate is limited
to 15 FPS. Testing has shown this is the slowest a game can run and still be sufficiently responsive. The game has
accelerated time, so render is called one or fewer times per turn (update frame), depending on game speed. Caveman is
a hybrid game that combines FPS/RPG play with with the ability to interact with the environment, as seen in The SIMs.
The are hundreds of possible actions, from drinking water to making a stone knife. When the player is perfoming an
action, they can't move or fight, and must wait for the action to complete. Action specific handler code is called
each turn (update frame). When they are not performing an action, they can run around like in any normal FPS/RPG.

rungame
--------------------------while !quit { if ((turn_counter % turns_per_render)==0) drawscreen // render if (player_doing_action) do_action else process_input increment_game_clock run_simulation // update limit_framerate }
do_action
-----------------------------
runs the action handler code for the current action.
action handler code typically increments a counter, and after some limit is reached, performs a success check,
updates the simuation as needed, and ends the action. While performing an action, input is limited to
changing game speed and stopping the action.


An example action handler: drink water. this increases the bandmembers water by one every 10 turns until it hits
100, at which point it stops the action:

drink water
------------------------------------------------inc bandmember[a].action_counter[DRINK_WATER]if bandmember[a].action_counter[DRINK_WATER] < 10 returnbandmember[a].action_counter[DRINK_WATER]=0inc bandmember[a].waterif bandmember[a].water>=100 { bandmember[a].water=100 bandmember[a].current_action=DO_NOTHING }


process_input
------------------------------------
processes mouse and keyboard input. input controls can be remapped. raw inputs are mapped to input controls
such as IN_LEFT, or IN_ATTACK. process_input polls the input controls at 15 FPS, and always polls at least
once per render. movement inputs are translated into a move direction of forward, back, left, right, one of
the four diagonal directions, or none. code is then called to move the player in that direction, and play the
correct movement animation. About the only unusual thing about the code is the fact that movement must be
processed before jump, for movement velocties at takeoff to take effect while airborne. Caveman uses realistic
gravity and physics modeling for missiles and falling creatures (including jumping). So just like the real
world, once your feet leave the ground, you can't change your velocity. its all about your initial launch
velocities. Movement must be processed before jump to set those velocities.







run_simulation
-----------------------------------------
this is the main "update" routine. updates are done on an as needed basis. so things that get updated every
frame are updated in do_global_frame2, and things that only need to be updated once per second are updated
in do_global_second2, and so on for global minute, hour, day, and year.void run_simulation2() // the "model stuff" engine{do_global_frame2();if (frame==0) { do_global_second2(); if (second==0) { do_global_minute2(); if (minute==0) { do_global_hour2(); if (hour==0) { do_global_day2(); if (day==0) { do_global_year(); } } } } }}



do_global_frame2
-------------------------------------------------------------------------
this is the main update routine called every frame. it updates: bandmembers (PCs), missiles, animals (NPCs), rafts,
and clouds. Note that attacks are resolved one second after they are initiated, and a hit is only scored if the
weapon hits someone at that point in time. Also note that surprise is modeled in the form of "freezing" for a few
seconds before an entity can move or attack. move_rafts2 models raft movement (duh!) but also moves bandmembers,
NPCs, and dropped objects which are aboard the raft. So you can actually walk around and place object on a raft
while its in motion. model_falling2 handles the physics modeling for falling entities. update_clouds updates the
cloud particle system based on variables from the weather engine.
void do_global_frame2(){int a;BMupdate_all();model_missiles(); // model missile movementmove_animals(); // move animalsif (last_active_animal >= 0) // model animal attacks { for (a=0; a <= last_active_animal; a++) { if (!animal[a].active) continue; if (!animal[a].alive) continue; if (!animal[a].attacking) continue; animal[a].attack_counter++; if (animal[a].attack_counter>=30) { animal[a].attacking=0; animal[a].xr=0.0f; animal[a].attack_counter=0; continue; } if (animal[a].attack_counter==15) new_resolve_animal_attack(a); } }if (last_active_animal >= 0) // model animal suprise { for (a=0; a <= last_active_animal; a++) { if (!animal[a].active) continue; if (!animal[a].alive) continue; if (!animal[a].suprised) continue; animal[a].suprise_counter++; if (animal[a].suprise_counter>60) animal[a].suprised=0; } }move_rafts2();model_falling2();update_clouds();}
do_global_second, minute, day, and year are similar, modeling everything from animals entangled in nets or
bolas getting free, to background radiation (old age).


And that's the basic code structure of the game. pretty straight forward, a fixed time step game loop, with framerate
limiter. The only unusual things are: FPS/RPG mode or SIMs style do action mode, and the render based on
accelerated time.

Part 6:
https://www.gamedev.net/blog/1730/entry-2260310-the-building-of-caveman-part-6-data-structures/

Part 4:
https://www.gamedev.net/blog/1730/entry-2259583-the-building-of-caveman-part-4-the-z-game-library/
Norman Barrows
The building of Caveman - Part 3 - Getting a handle on directx

Getting up to speed:
So i was going to be using directx and visual studio c++. All fine and good,
but i hadn't written a line of code in something like six years! So, first
came console mode hello world. then came directx fullscren mode and clearscreen.
then came the usual beginning direct programs. draw triangle. draw
textured triangle, etc. took 5 programs over maybe a week to get back up
to speed enough to proceed.

Getting a handle on directx:
The api for the fixed function directx 9 pipeline is not what i'd call clean.
So a thin wrapper api was called for. i did a lot of research in order to figure
out how to make the pipeline run fast. what the pipline likes to see in the way
of calls, and what games tend to do in the way of calls are rather different.
The pipeline likes to draw large numbers of triangles with the same mesh, texture,
material, and other settings all at once. Games however, tend to draw objects
in a scene which can be made up of many different meshes, tetxures, materials,
settings, etc. The solution was a render queue. I call mine the drawlist.


Basic rules to go fast:
as a result of my research on how to make the pipeline scream, the following
mantras were adopted for graphics:
1. one texture per mesh
2. all textures 256x256
1024x1024 backgrounds are the only exception to mantra #2.


Structs, Arrays of structs, Records, Databases, and Lists:
I've disovered that games run on databases. the list of actvie entities is a
database, for example. all content assets of a given type might also be
considered a game datatbase. i usually implement my databases as arrays of
structs. required size is usually known, so a static array of structs is simple,
fast, and adequate. i will usualy refer to them as datbases or lists, rather than
arrays. and i usually call a struct in an array a datbase record. in these posts,
i will use the terms array, list, and database interchangably. and i will use
struct and record interchanably as well.


The drawlist (the render queue):
It was almost 2 years from the time that i wrote the first drawlist code to the
time i first heard the term "render queue". you learn something new every day!
the drawlist is a list of what to draw. you throw meshes (with textures,
materials, transforms, settings, etc) at it, and it draws everything in optimal
order for you. there's not much to it, a struct with all the info to draw a
textured mesh, like meshID, texID, materialID, mWorld, flags for alpha test,
cull, clamp, etc. these are kept in a list. there's also a 3 dimensional index
into the drawlist. its a list of all the textures, and for each texture, a list
of all the meshes that use that texture, and for each mesh, a list of the indices
into the drawlist of the individual records to draw using that mesh and texture.
Drawing calls add a record to the drawlist and its index. draw_drawlist() goes
through the index, drawing everything in (texture, mesh) order. the drawlist
includes a built-in state manager for drawing. the state manager filters out
redundant state changes such as changing mesh, texture, material, or settings,
when they are already set.

so the render queue (drawlist) and the wrapper api made directx petty easy to
work with, and run fast. Now it was time to build the game.

part 2:
https://www.gamedev.net/blog/1730/entry-2259488-the-building-of-caveman-part-2-what-to-build-and-tool-selection/

part 4:
https://www.gamedev.net/blog/1730/entry-2259583-the-building-of-caveman-part-4-the-z-game-library/
Norman Barrows
The Building of Caveman - part 2.

What to build, and how?

What to build? thats the first of a million questions to be answered when
building a game.

Well, the answer turns out to be pretty simple:

you build what you want to play, that somebody else hasn't already built
for you.

odds are, if you think its cool enough to play that you'd go through the
effort of building it just to play it, then maybe other people might
like to play it as well.

in my case, it was pretty easy. i'd already made over a dozen games over
the years. some hits, some ok, some that didn't sell much at all.

so you go with what works.

the line of thought was: "well, my biggest hit was probably Caveman,
so i probbaly ought to do that first."

So that settled what to build: you lead with your strong suit, your
flagship product.

Now, how to build it:





Platform:
PC. bigger installed base than mac and linux. insufficient
manpower (i'm an army of one) to support multiple platforms,
so PC is it. moble etc isnt even an option ue to the size of the
game - at least at first - pc alone is plenty of a challenge.





Language(s) and libraries:

WHATEVER:
1. HAS THE CAPABILITIES REQUIRED
2. RUNS FAST ENOUGH
and
3. WILL REQUIRE THE LEAST LEARNING AND DEVELOPMENT TIME

in my case, that was procedural ADT C++ code (look it up!), and DX9
fixed function. i'd been coding C/C++ games since before OO syntax,
and didn't really need the OO language extenstions, thus the
procedural ADTs. And Caveman has a paleolithic setting with no
magic or fantasy elements, so not much in the way of special effects,
smoke, flames, clouds, thats about it. so all i needed was
aniso-mipmapped textured meshes, with a little alpha test and alpha
blend. I do use a 2 stage textre blend for snow on the ground but
thats it. i was already familair with dx8, and hadn't learned
shaders yet - i hadn't needed them. still haven't really learned
them or needed them yet in fact. truth is, i'm a little scared
to get into shaders for fear i'll like it too much. the mere
concept of such power to "party on the bitmap" is...
intoxicating .




At this point i should mention that Caveman was developed on a
budget of $0. one baseline $400 pc (no graphics card), and
internet access, thats all you get to work with. no money for
tools, content, middleware, etc.



Compiler:
ms visual studio of course. i started with basic, then pascal,
then watcom C, all in the quest for speed. sometime in the mid
90's microsoft got a clue and added the compiler optimizations
required for games to the free version of MC C++. And directx
tends to work better with ms c++ than with some other compilers.



3d modeler:
truespace rosetta 7.61 beta
1. its free
2. used to be the next best thing after 3dsmax
3. full directx .x save capabilities built in. no conversion /
import / export headaches.




2d paint programs:
paint.net and "free clone stamp tool".
both are free. paint.net has the basic capabilities of
photoshop. clone stamp takes up the slack. a really awesome
little texture painting program.


Audio tools:
TBD (to be determined)
i have xaudio 2 up and runnning but so far all it does is
play "time to relax", track 1 from the album "smash"
(as i recall) by the offspring as a test. it was the first
wav file i came across on my hard drive. the music for the
game is pretty simple, tom toms and flute for atmosphere,
funky beats reampped to jungle drum kits for combat. and i
have the general 6000 series 50 cd sound effects library
from Sound Ideas for foley sfx. but i gues i'll have to
build another bow. the sound of a primitive bow firing was
the only effect i had to make myself for the original version.
to do it, i made a 7 foot long bow, with fishing line for
the bowstring, and relatively straight wood sticks for
arrows. having paid $1000 for the sound effects
library, i was bound and determined to get my money's
worth out of it. so i used the box as the mike stand
while i recorded the sound of me firing the bow down
the hallway in the house! .

up next:
step one, getting a handle on directx graphics.

part 3:
https://www.gamedev.net/blog/1730/entry-2259520-the-building-of-caveman-part-3-getting-a-handle-on-directx/

part 1:
https://www.gamedev.net/blog/1730/entry-2259487-the-building-of-caveman-part-1/
Norman Barrows
The Building of Caveman, Part 1

in this series of posts i will explain the process of making the game
Caveman v3.0.

What was done and why.

this is done in hopes that it may help others in their quest for success.

first, who i am:

i'm life long gamer.

as a little kid (before computers!), wargames facinated me.
that big hex map with all that terrain, and all those unit counters! and the
dice and the charts! WAY too cool! there was something seriously cool going on
here!

then there were arcade games like "night bomber", a very rudimentary
b-58 hustler flight sim arcade machine. it had a backlit plastic 3d
conveyor belt of plastic for the ground mesh, and used some sort of illuminated
platic for the missiles in flight. then it was mirror projected to the view
screen of the cabinet. explosions were backlit under the "terrain mesh".
i can still remember the "shoo,shoo,shoo - booom, boom, BOOM!" sound effect
it made when you cut loose with a salvo of missiles. . i was so little i
had to stand on a stool to play!

later came D&D, then Traveller, then both at once! Two years as president of
the wargame club in high school. I also designed few table top games during
this period. There was a super RISK type wargame with land, sea, air and
carrier units, land, sea, and air bases, and land sea, and air zones for
movement. There was a sharship combat game, turn based strategy with carriers,
battle crusers, fighters, and such. and there was a simple futuristic RPG
inspired by the first star wars movie, set in a huge abandoned space station,
the rules of which were made upduring one of those rpg club convetions a
friend took me to once. Everyone always wanted to play in my D&D world, i was
almost always the DM, seldom got to be a player, but i learned as a player
from a great dm (thanks John Durfee!). i learned one of my first and best
game design lessons there: NO MONTY HAUL!


About the same time, i took my first
programming class (sophmore in high school i think it was). interperted
basic on an ibm 360 mainframe accessed remotely via 3 dumb tremials and a
teletype terminal, each running at 1200 bits per second. . I wrote a
turn-based clone of the arcade game "lunar lander" on that system. you'd
input your x and y thrusts, and then it would draw the next frame .
You should have seen how many trees i killed when i ran it one time on the
teletype terminal. to clear the screen and draw the next frame, it did a page
feed. so every frame printed an entire page on an 18" wide line printer
teletype terminal. the classroom was awash with printer paper! .

after high school, i used my buddy's mom's pc to write a text and vector
graphics turn based D&D game. it was a sperry rand pc with 64k, 2 360k
floppies and a CGA card. interperted basic on dos 2.11.

a couple years later i got my first pc, an 8khz 8088 overclocked to
10khz. word was, for second year physics lab, even if your experiment was
unsucessful, if you could write it up correctly with all the charts and
graphs etc in a word processor, "real professional like", you got an A.
so that was my justificsation for getting a pc. now adays its almost
de-regeur, i'd assume. at first i wrote a few apps in basic: text editor,
autoscheduler, quicken-type software, etc. and "flying saucer shooter" -
think missile command vs flying saucers, and you get ground based lasers.
my first realtime arcade game!

then i got into developing operating environments and integrated packages
(look them up!).I learned how to write just about every kind of app.

flash forward about 6 years. By this time i had almost completed my
software engineering degree at OSU (i switched from aerospace when the
cold war ended). i had worked as a systems analyst for the air force
as my engineering co-op, specializing in needs analysis and puchase
recommendations for executive information systems software (looks it up!).
i had also worked for the OSU school of medicine dept of anatomy where i
specialized in needs analysis and purchase reccomendations for
multi-media computer network systems (they were putting Gray's anatomy
online for the first time ever anywhere - in high resolution full
living color).

one day, me and a buddy were DLing Star Trek games, and they were all
the same top down view, sector quadrant, turn based text mode trek game.
i said, "I could write something better than that!" - and in six weeks, i had.
showed it to my friends. they said, "looks cool! needs better
explosions!" (you know how players are! ). so i added better explosions.

then i sent an email to the sysop of the biggest BBS at OSU, and asked him
very politely how he thought i ought to distribute it. he replied that the
model recently established by a new game called "castle wolfenstein 3d",
where you gave them a little for free (about 5% of the game - roughly $1
worth of content at wolf's originl price point of $20), and if they
liked it they paid for the full version. sounded good to me. but my game
was a mission based starship flight sim, not a level based shooter.
so i limited it to 4 types of missions (out of 10) and 10 missions
completed max (vs unlimited).

then i posted it to the local bbs's. about 3 weeeks later someone uploaded
it to AOL. i didn't even have an account. it became a top 10 download of
the week on AOL, with over 10,000 copies downloaded the first week.
all thanks to good copy in the file description (marketing, marketing,
marketing!), and a cool game to back it up. for a flight sim it was
pretty easy to play, as you could automatically lock onto any target with
the computers, and fly to it or attack it.

SIMTrek was followed by many other games, apps, and utilities on and off
over the years (this is the fourth time i've started or re-started my
software company).

i was blessed with a second hit when i restarted Rockland Software
Productions for the second time in 2000. Caveman v1.0 was picked up by
the local NBC news in Washington DC (where i live) as a last minute
xmas gift. response was so great it crashed the website!

about 3 years later, i got cracked by a team out of eastern europe.
the cracked version of Caveman v1.3 was posted on a warez site in
Africa, hosted on servers in Russia. it was probably on other warez
sites as well. losses were so bad, i had to fold the company.

in 2006 i started developemnt of caveman 2.0, a full first person
view virtual world implementation of the game, and almost finished it.
but i lost funding and the project had to be abandonded.

in 2012 i re-started rockland software productions for the fourth time.
so i was (re)starting my software company from scratch. what to build?

this is where the story of the building of Caveman begins...

part 2:

https://www.gamedev.net/blog/1730/entry-2259488-the-building-of-caveman-part-2-what-to-build-and-tool-selection/












,
Norman Barrows
that's right...

after taking a break and evaluating skyrim, my motivation levels were back up. A final push, and as of 7pm eastern time, march 24th 2014, the basic game is done!

and my original estimate of how long it would take was pretty accurate: about 2 years, working full/over time.

all that's left is adding on new high end features.

well, that and audio....

but the music is simple native tom-tom and flute stuff, the main theme music has already been written (for version 1.0), and the combat music is just a decent funky 4 measure drum loop remapped to a cool jungle drum kit. it would be trivial if my Alesis drum machine still worked. I plan to DL some freeware tools for that. voice acting consists of 40 samples, 20 male and 20 female, saying the 20 syllables in the caveman language. then you just play them in the correct order to say anything in the caveman language. i may just have them say random syllables. but i do have to get the infamous "bah-gah-bah" in there.

In the original version, it played like a FPS when you were in combat, a settlement, or a cavern. the rest of the time it played like TheSIMs. you'd pick an action from the actions menu, and your caveperson would say "bah-gah-bah" as if saying "Ok", then go off and do the action. when they finished, they'd return to the center of the scene, and say "bah-gah-bah" again, as if saying "all done!". for the foley effects, i have the general 6000 series 50 cd sound effects library from Sound Ideas out of canada. so i'm good to go there. paid $1000 bucks for it at CGDC '96. one of the best gamedev purchases i've ever made. and about the only content i've ever paid for. well, that and pyromania 1 and 2.

and it doesn't do animations of your cave person performing an action such as picking berries, like the first version did. but it can draw a mesh or multi-mesh model in a character's hands, and it can draw every object in the game, and there;'s the built-in animation editor. so its simply a matter of making some generic animations for different actions (pick.ani, hammer.ani, etc) perhaps 1-2 dozen (watch - it'll be 50! ). then add some code to set the camera for a good animation view, set the model's animation to "hammer" or whatever, and just draw that as the action completes. right now it just draws whatever's in front of the camera, with a progress message - no animations of you doing stuff.

i've divided the remaining new features/improvements into: code, graphics, and new: actions, skills, animals, objects, etc.

remaining coding tasks have been divided into: important, less important, and "even less important". there's also an "easy" category, which i'll knock out tonight.

here's whats on the "important" list. some folks here may recognize some features they've offered suggestions about. this is a direct copy and paste from the Caveman todo list, so you can see the actual workflow document (note the time estimates on some features): * option to disable windows key - need to get online * adjustable diff levels for:healing ratehit pointscaveman encountersanimal encountersparty strength (affects number appearing) * number keys for select wpn * yield, surrender, and parlay1 DAY * add thief encounters. guy walks up, "gimme all your stuff or else!"1/4 DAY * add slaver encounters. attack to subdue. capture. model escaping, etc. - REQUIRES ADDITIONAL AI 2 DAYS * kidnappers: like thieves and bushwhackers and slavers. attack to subdue. take captive, but to ransom you back, not sell off as a slave.1 DAY - ONCE SLAVERS ARE DONE * add encounters with traders who do settlement trader's hut type trading (IE professional traders). make friendly cavemen just do "what i got on me" trading (amateur traders). * questgen1 WEEK * raiding and inter-band rivalry/conflict, and forming alliances with other bands. 3 DAYS * romance, mating, offspring.tied to relations. mate action on talk to cavemanneed very good relations + opposite sex + physical attractionneed to track who is who's mate, and who has no matemating may lead to offspring.pregnancy: food, water, sleep go down faster. fatigue goes up faster. movement rate goes down. damage or illness has chance of causing miscarriage.physical attraction: charisma, but more - phermones - genes. compatability. hmm...the 6 way street: 2way like, 2 way love, 2 way lust.3 DAYS
the questgen is big, probably a week. mating may take more than 3 days. diff levels and hot keys, probably less than a day. yield, parlay, and
surrender, a few hours. but then you have to model escaping too. All the new encounter types can be done in 2 days. raiding might only take
2 days, maybe just one, or even less, basically its just triggering an encounter. but you do have to model who's at war with who.

wait and see how it goes.

time to get back to work.

whats next?

ah yes, give the player a chance to recognize
they're lost and stop travelling cross country.
add an is_lost variable to a bandmember struct.
set it to false when they start to travel cross
country. set it to true if they get lost.
when you check for getting lost, if they're
already lost, check for them recognizing they're
lost, and stopping.

til next time...

code once, test twice!
Norman Barrows

status report 6-15-2016

the game is really coming along. its now complete, stable, and appealing enough that i've started playing it just for fun, instead of playing store bought games i own. "resource tracking on a per resource basis" and "lack of variety in diet affecting mood" have really made gathering and preparing food more realistic, challenging, and interesting. the addition of large and small flint points and pitch has made crafting much more realistic. it amazing how small rule changes in the mechanics of a game can result in big changes in the dynamics of a game. those two small changes have raised gameplay immersion levels by amazing amounts.

highlights of new features:
* about 20 types of quest generators completed so far
* A* (A-star) path finding
* wood palisaides
* villages
* cave painings
* numerous new actions and objects

big stuff left to do:
* quest generators
* final graphics

new since the last status report on 3-17-2016:
* game tracks last 10 types of food eaten. eating the same thing too often reduces mood.
* followers and pets now moveto owner at rng 50 and stop at rng 10.
* TAMED no longer migrate as part of graze behavior
* added msg if your pet gets killed
* all damage in the game now uses hit loction. old damage variables turned off.
* crippling affects animal and NPC movement rates and attack capabilities.
* give some indication of effect upon eating a spirit plant.
* changed popup & fullscreen menus and messages to dark stone background for greater contrast with white text.
* fixed: selected_pet wasnt hooked up.
* make BMs move out of your way when you push against them. when player collides w/ bm doing nothing, they go into collision recovery.
* fixed: sleep in bedding in hut, then get stuck in walls when wake up. sleep in bedding snaps them to bedding center. bedding center is in walls. select picks spot 3-4 feet in front of BM, inside hut. beding is made 5 feet infront of BM, and thus inside the hut walls. simply create bedding at BM loc, then its guaranteed to not be inside the wall! : )
* pets should reduce chance of being suprised - should raise alarm when encounters happen. pet within 100 cuts BM chance of being suprised in half.
* option to switch to bm if they are attacked while doing an action
* companions, warriors, and pets move out of your way when you push against them.
* make b2_avian_eatkill use meat resources, not general resources.
* defend berrybush, fruit tree and waterhole quests now fail if player flees.
* fixed: rockshelter collisions not working. terrain_in_way and terrain_in_way2 now use rockshelter_in_way instead of collision map. a rockshelter is not a BBox radius type obstacle. generic_move2 and b2_bm_move didnt do collision checks when BM was inside rock shelter. so BM sometimes got stuck in collision against interior walls.
* fixed: silvatherium attacks CHR cavemen at rockshelter, and they just stand around. friendly AI was just targeting HOSTILE, not WILD.
* new_set_tgt must consider all potential threats when selecting targets, or fight or flght won't work. so friendly AI must tgt WILD and hostile for
example.
+ wild should target: all but their own kind.
+ goodguys should target: hostile and wild
+ hostiles should tgt all but hostiles.
+ nobody targets captured or subdued
WARROIR, COMPANION, and TAMED now use set_friendly_tgt (all wilds/hostiles) set_wild_tgt and set_hostile_tgt are already correct. set_friendly_tgt was fixed yesterday.
* fixed: forgot to add "staunch wounds" to the "other actions" menu.
* fixed: dropall and a number of similar routines (give_all, storeall, etc) didnt check for active object. all were code used by routines that call
shift_select. checked all calls to shift_select, to make sure all such routines were fixed. this was the cause of nuts appearing in storage pits!
* defend quests: takes too long for badguys to show up. i'm defending a rockshelter. gotten a dozen animals encounters (its a busy place!), but no badguys. been there almost a day. increased raid chance from 1 in 100K per sec to 1 in 10K per sec. avg of 2.77 hrs, vs 27.777 hrs between encounters.
* fixed: boost stats doesnt un-cripple limbs
* cut encounter ranges for dense terrain types (woods, grass, jungle, rocks)
* volcano collision rad is off - increased to 55. volcanos can span more than 1 collision map. so portion in adjacent map doesnt get set to impassable. will need different collsion check for volcanos. removed volcanos from collision maps. now uses true 2d rad of 70 ft.
* not many volcanoes in the world? increased chance 10x
* fixed: quest_animals_removed doesnt work with multiple simultaneous defloc encounters.
+ add is_quest_animal, quest_BM, and questnum to animalrec.
+ set is_quest_animal etc when spawn quest animals.
+ add animals_removed to questrec.
+ make remove_animals set cm[quest_BM].quest[questnum].animals_removed if it removes a quest animal.
* new: need non-defloc aggressive quest AI. if quest animal and not defloc, runs new quest AI.
new quest gens:
defend rock shelter
defend hut
defend berry bush
defend fruit tree
defend waterhole
moveto
transport
get special item
volcano quest
* attack cave rockshelter and hut quests done
* new fuction: dist3. bbdist in ft. works up to 75K map sq range! (375,000 miles!)
* fixed: crh encounters now spawn outside the crh
* replace quest_animal_alive (any defloc alive) w/ quest_animal_alive2 (any quest animal for cm1 quest q alive)
* replace quest_animal_removed with cm[a].quest.animals_removed
* done: all quest gens: must trap out player simply fleeing and quest animals deactivating.
* checked volcano quest. double spawn is fixed.
* kill animals for reward - places animals at a map sq too far from questor, should be 1-2 sq radius. same for kill cavemen for reward. made a version of randommapsquare that takes a radius.
* MOVETO_QUEST (find treasure) need to change view_quest message when quest begins. general loc is now known. no longer random chance to find treasure, now must look for it.
* refactor: getter and setter functions for animal, caveman, and worldobj locations. make them call generic location getter and setter fucntions.
* success, fail, and encounter wavs for quest messages.
* fixed: friendlies talking to you: if they ask you to talk and you say yes, and you do a talk action, they greet you again after the talk action. didn't set flag to "npc done talking to player" when called the talk action. so after the talk action, it went back to NPC talks to player again.
* increase clip rad of gt moa
* fixed: cant eat w/ 1 crippled arm. EAT was not set to 1 hand action. made a couple other actions 1 hand (like staunch wounds).
* crh hostiles get stuck in / spawn in rocks. uses something_in_way_of_animal for placement. 5 calls to terrain_in_way with +1 on rad - uses collision maps for rocks. uses terrain_in_way2 for movement. one call. uses collision maps for rocks. the +1 rad fix might take care of it. have to wait and see.
* make sure new items like teepees and bedrolls are on treasure tables.
* grunt sfx for npc talk to player dialog messages.
* success and fail wavs for talk to caveman menu
* to do an action, anvil stones can now be nearby or in your inventory.
* only one BM at a time can use a dropped anvil stone to do an action.
* picking up an anvil stone in use stops the user's action.
* tried lessons and advisor
* improved character creation
* improved tutorial
* set bleeding, crippled, etc to false when bring them back to life.
* anvilstone nearby fixed
* butcher time and butcher quality fixed.
* improved getstring
* improved fullscreen menu
* new: stone buttons
* make sure all calls to getstring say "Enter ..."
* add success and fail wavs to run_tutor
* checked: resource depletion by other bands works with the new resource levels by type
* fixed: BM_bestwpn and npc_bestwpn now take crippling vs 1 and 2 handed wpns into account.
* fixed: if missile tries to hit animal, animal sets all nearby friendly animals to "taking fire". even if animal is killed in a single shot, the rest have been alerted.
* fixed: terrain_in_way and terrain_in_way2 sometimes returned CM terrian type, not 1. made someting_in_way_of_animal not work. thats why animals were spawning in rocks.
* put trace on pet wolf AI
* added collsion map check w/ radius to something in way of animals. rocks are still an issue.
* fixed: run_pack_ai didnt check if animal was wild. only wild animals run pack ai. bug made tamed run pack ai sometimes.
* collsion avoidance doesn't look as far ahead, doesn't move as far.
* collision recovery ends sooner
* bandmembers, companions, warriors, and tamed now follow you in and out of caves and caverns, either as owner, or via follow orders.
* fixed: friendly leaders collision avoid cm0 while approaching cm0 to greet.
* fixed: 3pv cam vs NPC huts. adjusted check for crh huts to match that for player huts. added check for crh huts to 1pv vs 3pv code. wrote new routine to check abandoned huts. made it match the code for player huts. added that to 1pv vs 3pv code.
* fixed: 3pv: draw player in hut if doing action. if they are doing an action, it does not check for "don't draw in hut".
* fixed: 1pv skin tex not set when follower joins band
* fixed: 1pv skin tex not set for playtest game
* fixed: reduce radius for fire light and sound.
* fixed: no grass area around tarpit is too big? tarpit too small? its that darn 10 rez on the ground mesh. cant model small curves well. make tarpits bigger. increased rad of tarpit from 15 to 30.
* fix: lakes should be bigger. be sure to resize both graphics AND collision checks! increased rad from 30 to 100.
* implemented A*. still ned to test it, then hook it up to the game.
* spiced veggies: findtime and findchance were not set. was using defaults (1 hour and 1% chance of success). removed spiced veggies from the init lookup functions and into the regular init code.
* learn hafting: now requires flint knife, not cutting tool as part.
* change stoneworking to knapping. require flint, knapping tool, and one hand hammer to learn.
* changed time to make wood arrow from 8 hrs to 30 min.
* add large & small flint point objects. large to make knifes and spears, small to make everything else. 300 stoneworking to make small, 500 to make large. need small to learn hafting. draw lg point as flint knife w/o handle. draw small point as 1/3 size lg point.
* make cutting tool no longer requires knapping skill. make cutting tool now increases knapping skill.
* got 2d map A* working. long setup time. high ram requirements. fast.
* got traditional 2 list A* working. zero setup time. low ram requirements. fast. not fully optimized yet.
* fixed AI vs campfires. flee from campfire has its own vars now. wild animals don't target entities near fires.
* added fight or flight to predator respond to missile fire.
* collision map system now supports collision maps at arbitrary scales. no public API change required! just get_CM_index API used by A*.
* got A* fully working in the game.
* collision checks use high rez collision maps once again.
* added +0.5 x and z offsets for collsion map checks for "pixel perfect" accuracy (rasterization rules).
* "make small flint point" action now repeats as long as you have parts.
* social is zero, but not nuking mood. changed mood reduction from 23 in 10000 chance of 1 every 15 minutes to 1 every 15 minutes.
* make cordage: says no anvil stone on or nearby, when there is. there are 2 stones, one already in use. but use of second stone was stopped by combat. it still may be flagged as in use. added stop_usng_anvilstone if new action==DONOTHING to setaction.
* removed from game: (not realistic)
stone arrow
stone spear
stone javelin
stone atlatl dart
stone fish spear
throwhatchet.
flint throw rock
* add 1 wood required for flint knife. knife gets wood handle. need model.
already added leather handle requierd. added cordgae, hafting, etc. made new model.
draw 1pv wih doen't use new model yet.
* cook foods: ask how many to make? just make all? make up to 5 at a time? already makes as many as you have parts for. how many would require modding each try_cook(somefood) call.
* pet: check health: left arm hp/dmg: need space after "l arm".
* as friendly leaders migrate and get removed, each new leader come up to player and greets them. should set all nearby friendlies to will_talk=0 when set leader to will_talk=0. now sets all freindlies within 500 feet of player to done talking when leader is done.
* quit to desktop from ingame menu
* fight or flight: should do flight for some period of time, not just until reach edge of fight or flight check range.
* CRH npc asks to trade: should do band trade, not individual trade.
* add band dialog action: ask ally to join village (move next door). checked and done!
* if CRH band is ally and shelter is within 500 of player's shelter, divide migration rate by 10 when changing the map. checked and done!
* villages - ally invite to join - add build hut quest. checked and done!
* allies should not ask you to join village if they have already asked you and you accepted but have not buiilt a hut there yet. checked and done!
* allies should not ask you to join village if you already have a hut near them. checked and done!
* added log object checked and done!
* heavy objects are exempt from container rules checked and done!
* heavy objects need only be nearby to use them checked and done!
* mark and unmark nearby objects as in use checked and done!
* added gather logs action checked and done!
* added wood wall object checked and done!
* added make wood wall action checked and done!
* inspect wood wall action checked and done!
* repair wood wall action checked and done!
* demolish wood wall action checked and done!
* select dead BM, take stuff. checked and done!
* add ability (IE action) to check resoureces in the area, see how low they are. . checked and done!
* new: fish with fishnet action. new object: fishnet. - already done : )
* new: add fishing actions to shoreline/ocean terrain . checked and done!
* new action: play with water. all water terrains. . checked and done!
* watch sunrise action . checked and done!
* watch sunset action . checked and done!
* new action: watch snow fall, like watch clouds.
* chat w/ bm action . checked and done!
* chat w/ npc action. . checked and done!
* catch rain in mouth (drink)
* catch snow in mouth (drink)
* select snow on the ground
* eat snow on ground for water. water goes up, but food goes down a little.
* melt snow with fire to make water
* shoreline: swim in ocean action . checked and done!
* select cave interior wall. checked and done!
* make cave painting. checked and done!
* new: obsidian resource. better than flint.- fisrt and easiest thing you can do is make it so you can find flint (obsidian) at volcanoes. then you can add the obsidian object (like the flint object), and the small obsidian point, and large obsidian point (takes less time, lower chance of success, higher value than flint). and then add obsidian weapons and tools. or just use obsidian to make higher quality flint tools. add obsidian object, only gather ta volcano. add small and large obsidian points. when make stuff with points, give option to use flint or obsidian, if obsidian, increase quality of weapon. or just add obsidian object, and give choice of obsidian or flint when making the point, and it increses the quality of the point, which will ultimately increase the quality of the tool or weapon. thats the easiest way to do it. so add obsidian object which can be gathered near volcanos (say within 300 feet). when make points, give optin to use flint or obsidian. if obsidian, increase quality greatly, or simply have obsidian always be 150 or 200 quality or something. coyuuld just have it display the name obsidian if the quality is high, and obsidian is just a high quality type of flint. that would be even easier. so add "gather obsidian (high quality flint)" to the volacano menu. takes a long time? yields high quality flint. requires one hand hammer stone?. checked and done!
* new object: pitch. a glue used for hafting etc. can use tar or tree (pine) pitch. to harvest pitch, wound the tree, then wait a few days. hmm - how to model that? woods - cut trees for pitch. woods gather pitch. must cut trees for pitch first. marks trees as cut - with a timestamp. when harvest, only
proceed if timestamp is old enough. clear timestamp when they harvest. make pitch use wood resources. make all objects require pitch for crafting. can't use tar yet, just pitch. checked and done!
* add to game: S_MEATVEGGIESTEW. already in there: S_STEW. object #31.
* new object: sundial compass stick. stick it in the ground pointing at the sun, and wait 15 minutes. the shadow points roughly east.
* find sundial compass stick action
* use sundial compass stick action
* researched deadfall traps, fishtraps, and tanning
* changed requirements for making tanned hide.
tanned furs:
1. scrape inside w/ scraper
2. mix brains and water, coat inside(?). soak & stretch to soften. - smoking can hasten process somewhat?
3. smoke to make insect repellent (optional)
tanned leather:
1. remove fur - scrape
2. scrape interior
3. soak in brains and water and stretch to soften - smoking can hasten process somewhat?
3. smoke to make easer to re-soften after getting wet. (optional)
so they are both basically the same, one you scrape off the fur, the other you don't.
so a tanned hide can be with or without fur.
and it requires brains, water, a clay pot, a scraper.
and all items should be made from tanned hides, not hides.
need to add brains object.... ugh could assume they take the brains with the hide... and the weight and value represents the hide and brains as a "package deal". until they make a tanned hide. then the brains are there, ready and waiting. except for buffalo, whose brains are too small to tan their own hide. no need to track eacj part of a butchered critter, just one per type of use. so for tanning you need hides and brains - in equal numbers, and you get hides and brains in equal numbers. so unless you get into trading them, you can just track one, and assume you always have the other to go with it - the "package deal" concept. so a hide is really more like "tanned hide parts".
* all objects now require tanned hide for crafting, not hide.
* removed tanned hide armors. all hide armors are now tanned.
* removed stone and shell armors - unrealistic.
* add funnel fishtrap object - inventory item
* add make funnel fishtrap action
* add set funnel fishtrap action. requires bait and stream, river, or lake. sets timestamp.
* add check funnel fishtrap action. check timestamp, add fish and remove bait
based on elapsed time.
* wood stake fishtrap object
* draw_dropped_obj draws wood stake fishtrap at water level
* make wood stake fishtrap action
* inspect wood stake fishtrap action
* repair wood stake fishtrap action
* abandon wood stake fishtrap action
* bait wood stake fishtrap action
* check wood stake fishtrap for fish action
* maps as treasure on dead hostiles. adds info to player's world map.
* musical bow object
* make musical bow action
* play musical bow action
* added stufflist_wt_carried_in_containers() routine
* added stufflist_container_capacity() routine
* added stufflist_needs_more_waterskins() routine
* added stufflist_can_carry() routine
* added encumberance checks to transfer_items
* added container checks to transfer_items
* added waterskin checks to transfer_items
* added transfer_items to storepit menu
* added transfer_items to travois menu
* added transfer_items to bandmember menu
* added transfer_items to exchange items with companion menu
* added avoid_cliffs() to B2_avian_common_part1 AI. if below and just south of cliffs, flies south and climbs.
* defend friendly questgen:
player hears of friendly w/ quest. player goes to friendly. freindly aks for help killing badguys threatening them. player hangs out, badguys attack. if friendly survives and all badguys dead, player gets reqqrd and end of quest. if frieindly dies, no reward and end of quest. if badguys get away, trigger another badguy attack after a while.
* random_active_npc now handles "all known npcs already active in sim".
* new_npc now returns -1 on error.
* all calls to new_npc now handle -1 errors.
* all calls to set_npc now check for npc_in_sim first
* fixed: add_gift_giver now checks for npc_in_sim already. was just adding the first npc found. used by both gifts and call for aid. get both at once and it tries to add the same npc twice - and BOOM! animation controller collision!
* turned off title screen
* allies ask you to join village: menu() is off by 1.
* fixed: allies give gifts too often?
* fixed: allies ask for help too often ?
* fixed: allies ask you to join village: menu() does yes when you click no and vica versa.
* decreased time for find wild veggies
* increased food boost of wild veggies
* fixed: gather pitch was missing from savanna and jungle menus.
* sleep goes down 10% slower on average.
* reduced frequency of ally gift encounters
* reduced frequency of ally call for aid encounters
* fixed: search dead BM: shows player's inventory, not dead BM's. says "give 10" and "give all", not "get 10" and "get all"
* exit cave: place them a little farther from the entrance.
* can't select cave interior to paint cave painiting!
* collision rad for cave interiors is a little too big.
* don't let them select the sky when inside a cave.
* adjutsed time and chance to find flint.
* fixed: gather pitch: shows fail msg after showing success msg.
* fixed: dont let them start gathering pitch if another BM in the map sq is already gathering pitch. action stops if another BM gathers the pitch first.

Norman Barrows

Major changes to Caveman v3.0 since the August 2015 release of beta 21:

ENCOUNTERS: (encounters and treasure now scale to party size AND level, not just party size)
* regular and quest encounter number appearing now scales based on party strength (numbers AND combat skills), so you get tougher encounters at higher levels of experience.
* regular and quest encounter treasure quantity scales with number appearing - which is now based on party strength (not just party size).
* quest treasure quality now scales with party strength.
* time of day now affects encounter chances.
* remaining resource levels in a map square now affect encounter chances in that map square.

COMBAT:
* hit location and damage by body section, including cripppling, and its effect on combat, movement, and all actions.
* modeling of critical hits.
* modeling of bleedout, including a new "staunch wounds" action.
* check follower health and and check pet health actions (shows dmg by section, bleeding, etc).
* separate weapon hotkeys for each bandmember.
* new orders: alarm! (IE battlestations, we're under attack), stop what you're doing (stop current action).
* new action: automatically walk around and pickup ammo. super nice when you have dozens of arrows lying about after a combat.
* you can now change weapons while attacking. you just have to start the attack over with the new weapon, thats all.
* option to switch to bandmember when they get an encounter if they are not the current bandmember (IE the one the player is currently controlling).
* new skill: combat defense. works in lieu of hp increasing with level. refects you getting better at defending yourself.
* new combat move: dodge. quickly moves you fwd/back/left/right.

AI:
* new land predator AI.
* new defend location AI (used by quest encounters).
* new pack hunter AI.
* new domesticable wild animal AI (used by dire wolves).
* new follower AI
* new pet AI
* new combo defend/maintain distance AI - maintains distance from stronger animals, stands its ground and defends against others.
* respond to missile fire by animal type (all of a type attack or flee, not just the one fired upon).
* animals flee from campfires.
* graze behavior (wander, stand, flock, migrate) for pets.
* improved (but not perfect yet) AI for avians vs cliffs.


BUTCHERING ANIMALS:
* re-balanced amount of meat, hides, tendon, etc from animals. amount of meat, bone, hides, etc on an animal is now based on animal weight. all animal types now have a weight. the heaviest being Stegodon (a type of mastodon) at 28,000 lbs.
* individual tracking of meat, hide, bones etc left on a carcass (and thus available for harvesting). also tracks meat quality - and meat on carcasses spoils over time too.
* butcher actions now take a short amount of time, and yield one unit of resources, and continue until you can't carry any more or there are no more resources. it used to take a long time (like a week for a single caveman vs a mammoth carcass!), then give you everything at once.
* multiple band members can now butcher the same carcass simultaneously.

QUEST GENERATORS:
* implemented 20 of 50 planned quest generators.

NEW OBJECTS:
* teepee - basically a portable hut.
* bedroll - basically a portable version of bedding.
the teepee and bedroll combined with the travois means you can now take your entire camp with you.


ACTIONS:
* you can now build huts and lean-to's in caves.
* new dialog option: greet caveman
* new dialog option: exchange news - added in anticipation of adding fame and / or reputation to the game. exchanging news is how fame/reputation will travel.
* added ability to abandon quests you no longer wish to pursue.
* success or failure sound effects and messages for all actions upon completion

TRADE:
* re-balanced trade value of items
* traders now have limited quantities of inventory and money.

SIMULATION:
* max band members increased to 50
* max active animals increased to 500
* water stat goes down faster in hot weather.
* band members now stop most actions if their mood drops too low.
* improved domestication of dire wolves.
* local temperature now affects rate of food spoilage.
* tracking of resource depletion in a map sqaure on a per type basis, instead of as one generic resource.
* resources now replenish continually over time, instead of being totally depleted for a while before instantly replenishing to 100%.
* wild vegetables and poison plants are now found in more types of terrain.
* ability to exchange items with your travelling companions. includes tracking ownership, yours vs theirs. and NO, you can't take their stuff!
* gifts to NPCs now appear in their inventories.
* automatic transfer of liquids with containers finally works correctly.
* rafts have now been fully implemented. collision checks, AI, actions, etc - everything works with rafts now. during testing, it was very cool sailing across the ocean on a raft with one other band member, one companion, and my pet dire wolf, and seeing a new shore for the first time.
* friendly cavemen now have a chance to come up to you and initiate various dialog actions when you encounter them.

SKINNED MESHES:
* better animations
* correct materials
* correct pitch relative to the 3pv camera
* drawing of equipment

TUTORIAL:
* added making a wood spear to the tutorial - the stone knife object became a cutting tool, which is no longer a weapon.

OTHER:
* numerous minor adjustments and fixes to game balance - god is in the details!

a long period of time was spent addressing the question of "what to do when you're high level?".
the traditional solution of "Build a castle, raise an army, conquer the world!" from classic Dungeons & Dragons doesn't really work that well with a stone age setting.
turns out, its not "what to do when you're high level?", its "what to do when you've done it all?" (IE used up all the content in the game).
players need a continual supply of compelling new content appropriate for their level, not that hard in classic D&D, given an obliging DM. somewhat harder to do in video games (free high quality DLC to the rescue).
regular and quest encounters and treasure that scale to player's level provides content of appropriate difficulty for the player's level.
the ability to generate 50 different types of quests (compare to two for Skyrim's radiant quest system) will help keep things from getting repetitive too quickly.
a quest editor is also planned, along with some number of quests made with it, such as one or more optional story line campaigns. this will allow users to create and share their own quests. which may provide another possible source for compelling new content appropriate for the players level (since quest editor quests -IE number appearing, and treasure quantity and quality- will also scale automatically to player level).

WHATS NEXT:
* the ability to build a castle - caveman style. IE wood, dirt, and stone wall objects.
* multi-band settlements (proto-villages) - ask you allies to move next door.
* making the player seek out a wide variety of foods so they don't just stockpile and eat nuts all the time (for example). eating the same thing all the time will nuke your mood. maybe your stats too if mood isn't enough motivation.
* there are still a number of new actions, objects, and animal types still to be added to the game.
* misc features such as browsers for object types, animal types, and actions.
* multi-band gatherings
* mini games:
- stick and hoops (similar to lawn darts)
- chunkey (hit the thrown ball with the spear)
- non-lethal combat
- archery competitions
- and of course "rock", as in rock-paper-scissors. but its the stone age, so its before they invented paper and scissors, so they just have "rock". pretty boring game actually - always ends in a tie! .
* boss monster and boss caveman encounters for high level players.
* final graphics

and then it will finally be time to release. smile.png


there is currently no beta demo available. the public beta was a smashing success, with only two users reporting issues, one related to startup resolution detection, and the other unrelated to the game (corrupt UAC file interferes with install/launch, causing the dreaded BEX error). as a result, the startup resolution detection code has been beefed up to explicitly check caps, instead of assuming that caps which should be there are there.

I may run a semi-private beta for folks interested in providing feedback on gameplay. not sure at this point. if anyone is interested in something like that, PM me. you can never have too much feedback - whether you're talking games or guitars! . (yeah, i play the electric guitar).
.

Norman Barrows
Few progress reports, but lots of progress:

All Improved animal animations done - even the new bunny_attack animation! . The improved animations really bring the animals to life. Sometimes i'll be testing something and its like really being back in a paleolithic world.

Took some time out to finally give Skyrim a thorough evaluation. Oblivion was a major influence on the design of the original version of Caveman. I came away quite pleased with where Caveman is vis a vis Skyrim. Obviously i don't have the manpower to match them in graphics special effects or human generated content. OTOH, I found the changes between Oblivion and Skyrim to be evolutionary, not revolutionary. And it still suffers from the same design flaws as Oblivion (hard coded levels, hard coded spawn points, nothing to spend money on, rat in a maze level design, etc) plus new ones brought on by trying to add random encounters w/o thinking through the consequences (dragons, vampires, etc kill all the merchants!) and therefore not having a complete and balanced model/simulation (new merchants move in to replace those that die). Guess that's what happens when you try to add simulator type stuff to whats basically a level based shooter and don't think like a sim developer.

Spent some time (about week) playtesting and tweaking. Surprisingly little needed to be tweaked.

Implemented encounter ranges based on terrain elevation and vegetation cover.

Did final (?) improvements of caveman models.

Implemented all animations for 3rd person view:
stand
walk, run, and sprint - forward, back, left, and right.
sneak stand
sneak walk - forward, back, left, and right
climb mode stand (hang on) and walk (climb)
weapon ready
attack animations

graphics left to improve:
rain
falling snow
stars
prairie terrain
savanna terrain
cloud and sky brightness (both are a little dark right now)

this will bring all the graphics up to the minimum level acceptable for first release of this major new version of the game.

then there are a number of additional game play features to add, from interacting with snow, to taking rafts upstream, to high end stuff like raiding and inter-band rivalry, mating and offspring, etc. But these largely require just code, which i can kick out much faster than graphics. All the design work and rules have been worked out already, its simply a matter of typing it in. And for that i have Cscript, my secret productivity weapon.
Norman Barrows
[color=#ff8c00][size=7][font=arial]Progress report, August 28th, 2014[/font][/color]

Playtesting and tweaking continues. As they say "God is in the details!" Adjusting: collision radii, clip radii, research times and chances, gathering times and chances, etc. The Avian AI is still exhibiting odd behavior at times. Bandmember AI (AI that runs a PC when the human player is not controlling them) needs a number of minor adjustments.



Along with the usual "how things are going" info, i'd like to touch on a few ganmedev related topics that have come up since the last progress report.



[color=#ff8c00]

[font=arial]Project size and scope:[/font]

[/color]
Caveman 3.0 is big. Probably the biggest game i've ever made. Probably bigger than one person ought to try to do by themselves. I'm not saying it can't be done, its just a HUGE time investment for one person. If you're a lone wolf or small team, think long and hard before you take on a big project. If you do decide to go for it, it might be a good idea to build and release in stages. Start with the core game, then expand in future versions. Big projects that take a long time can lead to my next topic...



[color=#ff8c00]

[font=arial]Procrastination and burnout:[/font]

[/color]
About 2 years into the project, i found my productivity dropping. Team burnout was the cause. In my case, i reached my limit at 27 months of continuous maximum effort. So i'd take a break. But even after extended breaks, i was still not motivated. While surfing the web i came across "7 causes of procrastination and what to do about them". Turns out my cause was #2, burnout. So i switched gears a bit and spent some time evaluating other games, and watching tv and movies related to games i'm working on. I continued to work on Caveman, but not every waking moment. Sure enough, after a while, i was thinking about the project again while playing another game or watching tv or a movie, and soon after that i was motivated to get back to it fulltime. lessons learned: you gotta take a break every once in a while, and don't be in too much of a hurry to get back to work, wait until once again its what you really WANT to do. Spending time watching tv and movies and evaluating other games leads to my next topics...



[color=#ff8c00]

[font=arial]Faith:[/font]

[/color]
[color=#ff0000]WARNING: if you have never seen korean dramas or korean historical dramas, they are quite addictive, if you get hooked, don't blame me![/color]
One of the things i did while taking a break was i watched "Faith" for the second time all the way through. Its a korean historical fantasy drama set in the 14th century. A big budget production. And its got it all: action, martial arts, intrigue, drama, medical drama, fantasy, time travel, romance, comedy, you name it! All the production values are top notch: writing, costumes and props, sets, settings, special effects, anamie, subtle incorpration of fantasy elements in a beliveable way, casting, and acting (from what i can tell, i don't speak korean). The entire story is told in a one year long series, 24 one hour episodes, about the same as a long mini-series in the US. I can not recommend it highly enough if it sounds like your cup of tea. goto hulu.com and do a search on "Faith"



[color=#ff8c00]

[font=arial]A lot of my time playing games is spent waiting...[/font]

[/color]

The Sims: you queue up a bunch of actions, then you wait. or you wait while your sim sleeps or is at work. dont even get me started on the 4 minute load times for a small savegame file! And you can't speed up the game by much. normal speed appears to be something like 1 second of real time = 1 minute of game time. fastest speed is perhaps at most two or three times that.

Skyrim: not so bad, since its basically a big shooter. travelling to undiscovered locations for a quest. turn on continuous move and wait. and then there's the load screens. i actually find myself deciding what to do based on the fewest loadscreens required. IE i might sell or store an item at one location vs another because its 4 vs 6 loadscreens to get there. when loadscreens affect player gameplay decisions in this manner, it CAN'T be a good thing.

Rome II Total War: much of my time is spent waiting for other factions to take their turn, or waiting for troops in battle to march across the field to the enemy. And like the sims, the battles cant be sped up by much more than 2x.

Simcity: waiting for time to pass. again, you can't speed up the game by much.



[color=#ff8c00]

[font=arial]Low framerates:[/font]

[/color]
I was shocked to find out how low the framerates were for some games on my PC. The SIMs 3 in native resolution and lowest graphics settings clocks in at a whopping 9 to 12 fps! The graphics benchmark from Rome II Total War at lowest resolution and graphics setting can only manage 12 FPS. And this is on a PC that came out at the same time as these games. By comparison, Caveman 3.0 can do 15,000+ non-instanced meshes at 15 PFS all day long. And it can do over 100 animated charaters at once onscreen at 15 FPS. A typical complex scene (~10,000 meshes, perhaps 20 animated charaters) runs at about 20FPS with the framerate limiter turned off.



[color=#ff8c00]

[font=arial]Responding to input immediately:[/font]

[/color]
I've noticed a number of places in a number of games where the only response to input is a momentary or extended freeze or lockup. Saving in Rome II comes to mind. You click the OK button, and the game frezees. No "Saving game..." message or anything. The only signs of life are the hard drive light blinking. After some number of seconds, it comes back with a game saved message. I've also noticed a number of games are rather sluggish in response to input, as though clicks are being sent to some messaging system for processing by other components, instead of being handled immediately upon detection. Obviously i'm not privvy to the inner working of these games, and can only guess that its some morass of message queuing, multithreading with stalls, unoptimized middleware, and slow garbage collectors that causes this. divvying things up and passing messages everywhere may be a neat way to organize code, but it also adds overhead. i'm of the old school when it comes to how responsive software is: "If the task is not completed by the time i lift my finger from the key or button, its too slow!" perhaps this should be modified to "if it doesn't at least respond somehow by then, its too slow".



[color=#ff8c00]

[font=arial]The fastest code:[/font]

[/color]
"The fastest code is code that never gets executed". We've all heard it. Sure, it sounds great, but turning off code usually isn't a practical speedup. While playtesting, I've constantly been plagued by the fact that the simulation simlpy can't run blazingly fast and do complex AI for lots of targets all at once. Because of this, when i get an encounter, i usually move on until the animals are removed from the simulation due to distance. Then i get back to making a spear or whatever, once the simulation is running blazingly fast again. The obvious answer was further optimization of the AI. But i'd already been there and done that. Iterating through the list of active entities to select a combat target is the bottleneck, with no obvious solution in sight. Nothing less than a pardigm shift would change things. So i changed the rules! You can now only accelerate the game faster than 128x if there are no hostiles around. and at speeds above 128x it doesn't update non-hostiles at all (the AI code doesn't run at all). This simple rule change makes all the difference in the world. Now you can fast forward through actions with non-hostiles nearby at blazing speeds, and you still get full tilt AI when hostiles are around - having your cake and eating it too, very WITH my religion (as opposed to being against my religion).



[color=#ff8c00]

[font=arial]We now return you to your regularly scheduled progress report:[/font]

[/color]

recently done:
* increased sleep time by about 10%
* gifts to cavemen increase relations by twice as much.
* fixed: cant rest & heal in temp shelter w/ bedding when sick but not injured.
all rest and heal action triggers now use a single generic try_rest_heal() routine that lets you rest and heal if you have damage or are sick.
* fixed: getting encumbrance fatigue when not moving or engaged in sedentary activities
* added button graphics to view inventory and view help
* rest and heal now shows damage as well as progress
* added eat/drink actions inside shelters
* find reeds: doubled time required, cut chance in half
* fixed: doesn't moves followers when cross-country movement stopped due to encounters, etc
* fixed: companion's model and textures not copied to new bandmember when they join your band
* fixed: companion's 3rd interest not copied to new bandmember when they join your band
* fixed: companion's social not set when they join your band. now set to 100.
* turned off damage display over bandmembers
* added "add companion" to playtest menu
* added "add lean-to" to playtest menu
* AI recalcs best tgt every 2 secs, not every 5.
* fixed: doesn't draw rain/snow outside temp shelters
* fixed: new bm not set to alive when companion joins band
* fixed: not loading and saving cm[].alive
* increased MAX_RESOURCES in a map sq from 1000 to 2000
* cut crh resource reduction chance by 50%
* fixed: added renew_resources() to change_the_map(). 10% chance per day for depleted map sq to renew back to MAX_RESOURCES.
* added "issue orders". remapable "o" key input. orders menu: follow me, attack, and as you were.
* added "make sacrifice to earth godess gah" to rocks menu. volcanos only was too hard.
* sucessful sacrifice actions now boost god relations by 30. was 5 for fire, and 20 for all others.
* increased local automap range from just the players current local map sqaure (100 foot) to a radius of 2 local map squares around
the player's local map sq. range before: 0-100 feet. range now: 200-300 feet.
* game now runs fast with non-hostiles nearby! wont go faster than 128x with hostiles alive. doesnt run animals at speeds above 128x.
* button graphics for select_object screen
* button graphics for shift_select_object screen
* button graphics for trade screen
* increased collision rad: mixotoxodon
* increased collision rad: gt ground sloth
* increased collision rad: short faced bear
* fixed: animal encounters: start location not normalized
* fixed: caveman encounters: start location not normalized
* make bedding: shows progress %
* make bedding in shelter: shows progress %
* fixed: draws plants over bedding
* fixed: regen terrain chunks when build temp shelter
* fixed: regen terrain chunks when build bedding
* fixed: search dead CM: take one: doesn't remove item from list!
* fixed: action msg for making fire is messed up
* increased clip rad: world objects (due to temp wood shelter).
* increased action area for selecting temp shelter.
* 1 hand stone hammer: doubled time to find, cut chance to find in half.
* added /d command line switch: enables display of debugging messages as the program starts up.

beta 11 has been posted.

[color=#ff8c00]

[font=arial]Download and play it right now for free![/font]

[/color]

[font=arial]http://rocklandsoftware.net/beta.php[/font]




[font=arial]Thats all for now![/font]

Norman Barrows
Progress report - Nov 2013

Its been a while since I posted any info on the Caveman project, so I thought i ought to post an update. While I haven't posted any blog entries or screenshots in a couple months, I have been very busy working on the game.

As of the last update in mid-September, a first pass had been completed at making it draw everything in the game.

Since then, most work has been on polishing the graphics, with some optimization of accelerated time mode as well.

The following was completed in the last half of September:

adding dead as well as alive bandmembers led to the "code change in 99 places!" thread, and a bit of modding of the code. In the end, its refactored, better designed, more generic, and more flexible for use with adding further features.

a simple alpha texture blending effect was used to draw snow on the ground with fixed function pipeline.

a generic ground mesh generator was created, then added to the game to create cavern floor meshes on the fly as needed. its based off the "new way" chunk terrin mesh generator, and is generic enough to be used for caverns, caves, even the outdoor terrain meshes. but the chunk system works fine, so i see no reason to refactor it to use the generic mesh generator. however, all future features will use the new generic mesh generator.

I've actually done this with a number of systems developed for the game. For instance, there are three different internal API's available for checking the conditions for beginning an action. The original API could only perform basic checks, such as some level of a single skill and some quantity of a single object. The second API expanded this some. The third and current API uses a "requirements record", a struct that contains all the info related to performing an action: skills, tools, and parts required, in what quantities, and of what quality, and what skills you get experience points in when you successfully complete the action. Since the game has 100 types of actions, and many can be triggered in multiple ways (gather wood is an option on both the jungle and woods action menus), and since the older APIs worked fine for the actions that used them, i never went back and refactored them to use the newest API. When i do need to make changes to the trigger for an action, i update it to the new API if needed. This is basically a case of "not fixing it if its not broken", because there's so much other work to be done.

I added a Zaudio library to the collection of Z libraries - a wrapper for the Xaudio2 and 3D sound APIs, and made the game use it. So the game can now play wav music and sound effects. However i still have yet to create any audio content for it. at the moment it simply plays track one off of an Offspring album as a test. can't remember the name of the album. "self esteem" and "gotta keep em separated" are on it.

Npcs now use all types of weapons and armor in the game.

The code has been modified with conditional compilation to create both a full version and a demo version of the game. the demo will be used for beta testing, and as marketing material.

The game now lets you view the model of your player.

A caveman model viewer has been added that lets you view any caveman model in the game - of which there are almost 600 now.

As an example of the level of detail in polishing graphics:
The game now draws rain outside an overhang, not inside it, when you're under an overhang. Not even Oblivion does this. try it. goto weynon priory, and walk through the "tunnel" to the stables when its raining. it rains inside the tunnel! Actually most games do this, they draw rain if outdoors, and don't if indoors, and leave it at that. It was only after i added the effect that i noticed that Oblivion doesn't do it.

A generic random map API was created. Since pretty much all of Caveman is procedurally generated, it relies heavily on "pattern maps" of various sorts. these maps specify the location and orientation of things like trees, bushes, rock outcroppings, plants, as well as things like what ground texture tile to use on a given ground quad. The heightmap for canyon floors needed improvement. it seemed a perfect time to experiment with a generic random pattern map. the map itself is a simple structure:

int randmap[100][100]

its seeded with random values from 1 through 100. the map can then be interpreted as one sees fit for any purpose. for its first use, i wrote a routine that converted an x,z location to a y altitude based on the random map. x and z were dvided by 10, then modded to get the
"randmap[x][z]" indices. an flerp was done along the north and south edges, and then between the edges, to the the exact point between the corners [x][z] and [x+1][z+1] on the randmap.

the user interface for the game was changed. it no longer displays any stats whatsoever, no health bar or anything! instead, when a stat gets low, an "idiot light" such as "low water" or "high damage" is displayed. this seems to make for a much more seamless and immersive experience.

besides the above mentioned, the following graphics were done during late September:
falling snow
cavern interiors
chasecam view in interior scenes
improvements to clouds, sky, plants, skybox, dropped objects, and lighting.


October:
* drawing swamp
* bandmember attack animations based on weapon type.
* player's model animated in 3rd person view.

* i found an interesting typo / logic error (yes, a BUG in one of MY programs!):
frame % 15 == a,
instead of:
a % 15 == frame
this made NPCs numbers 15 and higher never select a new target, causing them to all run off in the same direction.

* new AI for bandmembers and npcs with missile weapons.

* hit points were re-balanced from body_wt / 10 to sqrt(body_wt). the old formula made big critters unrealistically hard to kill (like 100+ spear hits to take down a mammoth). now the hit points and damage system are in line with all available archaeological evidence as to just what it took to kill these animals.

* made accelerated time run faster. this was done by using the 2D indexes for the flatfile cave and hut databases. The game has 50,000 caves, 5000 rockshelters, and 28,000 huts. all in one big flat file database (array of structs). the first 50,000 structs are caves, the next 5000 are rockshelters, and the last 28,000 are huts. looping through 5000 rockshelters checking for encounters with their occupants isn't too bad, but doing so for 50,000 caves or 28,000 huts is a bit slow. so 2D indexes were created for caves and huts. the index is a 2d array whose indiecs are the map square coordiantes. the entry is a list of the indexes of the caves caves or huts in that map square. so encounter checks are only done vs the caves and huts in the player's map square, not all 28,000 or 50,000. this sped up the periodic encounter checks for occupants of nearby caves and huts. as a result, accelerated time runs much faster now. A fast as one game hour per real world second, while still doing full simulation of everything.

* drawing missiles (arrows, altatl darts, sling stones) in attack animations. it already draws the weapon, and draws the missiles in flight and on the ground.

* improvements to rain and falling snow effects

* improvements to rendering tall grass terrain. added a second grass texture, tweaked the textures for more realistic coloring. added functions that use the new generic randmap to do texID, scale, rotation, and offset for grass. Now the tall grass terrain is generated using a single generic random map - the same one used for canyon floor heightmaps! looks good. i'll post a screenshot.

November:

on october 30th i started on improving the animal animations. its now November 27th, and i'm still at it. The game has about 50 types of extinct mega-fauna in the game. each type of animal has 5 animations: stand, walk, run, attack, and dead. So far i've done about 40 of the 50 animals in the game. Each animal takes about half a day. The game is now up to about 110 animations total. reuse of animations between animal models has been less than hoped for. this is partly due to inconsistencies in the modeling. if a body mesh is flipped around end to end or upside down when creating the model, the rotations for the animations are backwards, resulting in some interesting moonwalk type results. This requires separate animations for critters with flipped bodies. next time, i'd just use two separate un-flipped body meshes so i could share animations. size of the animal also can make animation sharing impossible. large and small animals seem to need to move at different speeds. and the leg length of an animal has a definite effect on the speed at which a walk or run animation should play to sync properly with their forward progress across the ground. One of the big surprises is the stand animations. you would think that one generic animation of standing around doing nothing would work fine. As it turns out the stand and attack animations tend to be unique for each type of critter. Stand is where the personality of the animal comes out. so the stand animation for a hippidion proto-horse must behave like a horse, and camelops must behave like a camel, and silvatherium must behave like girrafe, and... well you get the idea... so slowly but surely i'm bringing all these extinct mega fauna back to life. testing the animations is quite interesting. You start a playtest game, punch up the animal in question, and watch it - like on animal channel! makes me feel like the crock hunter, or marty stoufer of wild america, or marlin perkins of mutual of omaha's wild kingdom. Once you've got them behaving correctly and have the walk sync'd up to the right speed, then you punch up some caveman encounters and watch the combat. this lets you check out the attack and run animations. after much back and forth of "this leg kicks out to far" and "runs still seems a bit slow", you eventually get something that starts looking and behaving like a real live animal. as i go along, some need improvements. i redid similodon populator (the biggest saber tooth ever) and homotherium (american scimitar cat) from scratch. Since taking on a saber tooth tiger was the original inspiration for the game, figured i HAD to have a better looking smilodon. so it was back to the reference photos, and the downloaded models to create a new set of limb meshes and assemble a new model. results were better than expected. A couple other models got minor adjustments to improve the head pivoting of the model for animation purposes.

i'm currently working on animal type 61, the dire wolf.

between the terrain graphics and the animal models and animations, there are times when its really starting to look like the real thing! which is much more than i could ever hope for - given that its just me, and not an army of graphics coders, modelers, and animators.

improved animations is last major thing for graphics. i'd really like to get some rayleigh in-scattering atmospherics going, but that will have to wait. there are more important features to be implemented. savanna and prairie terrains still need to be improved. looks like i'll be making my own acacia tree model from scratch, as none - even those for sale on turbosquid - looks correct. a few other minor things, and animating flames, is about it for graphics. Then its time to implement the remaining high level features such as raidung and inter-band rivalry. and then its test and tweak time, along with beta testing, and when its ready (but not until!), release!

wait, is that a speck of light i see at the end of the tunnel?
Norman Barrows
Progress Report - July 21st 2014

Beta 9 has been posted to the website, with a new download page:

http://rocklandsoftware.net/beta.php

I could really use some feedback on the game, so please, check it out! Its just a 60 meg download, and should provide about one evening's worth of gameplay.

Beta 9 now includes such niceities as real setup and uninstall programs.

Still needs audio and an opening animation. But I found a copy of caveman 1.0, complete with all the original wav files! And i still have source code for version 1.0, including the music player code. X Audio 2 based code is already in Caveman v3.0, - ready to go - just add wav files and stir!

I'm thinking I'll hold off on offspring, dinos, modern, an aquatic animals due to the time required to create the additional content (models, meshes, textures, and animations for babies, children, dinos, modern animals, and aquatic animals).

Audio and some serious play testing seems to be the next most important thing on the to do list for the project.

I also added a "quick tour" of the game to the website:

http://rocklandsoftware.net/caveman-tour.php

That's all for now!

Remember - keep your eye on the prize! Twice as important when its a big prize and takes a long time.

Hailing frequencies closed...
Norman Barrows
Progress report - it finally draws everything in the game!

After a long struggle with character weapons and equipment, Caveman now draws everything in the game. Some of the stuff still needs work, but at least it draws something.

I need to get caught up on posting screenshots.

making the cloud particle system use world coordinates as opposed to camera relative coordinates is next. Clouds are alpha blended. so they're sorted on range to camera. the world is divided up into chunks or "map squares" of 26400x26400 d3d units (feet), IE 5x5 miles. each map square has its own d3d coordinate system. when the player changes map squares, the world origin changes, so the camera's coordinates change to compensate. this sudden change of camera x or z by 26400 units changes the sort order of the clouds slightly (IE the difference between x,z locations 5000,0 and 5000,26399 for example). so the clouds "jump" slightly when you change map squares. the fix is to use world coordinates, IE map square x,z plus d3d x,y,z in the map square. when the player leaves a cloud far behind, its regenerated on the leading face of the could cube area that surrounds the camera.

the general idea of the cloud particle system is you have a cube shaped area that surrounds the camera (a "cloud box") that's full of cloud particles. info from the weather engine is used to move the particles, and to determine the number active. when a particle moves out of the "cloud box", a new position is randomly generated for it on the opposite face of the cloud box from the face it exited through. the cloud box moves with the camera. when the cloud box moves so a cloud exits the box, again, the new position for the cloud is randomly generated on the opposite face. by "on the opposite face, i mean in that direction and at that range from the camera - IE somewhere on that face of the cloud box.

right now the could system doesn't use world coordinates, and its not hooked up to the weather engine. it just draws 100% cloud cover and stationary clouds (IE cloudcover 1.0f, windspeed 0.0f, winddir 0.0f weather settings) for testing the alpha blend effect. but the api's already exist to hook the two together.
Norman Barrows

New orders menu code

New orders menu code

Here's the new orders menu code to go with the new bandmember AI

nothing fancy, it shows a menu, "Issue orders to..."
then a second menu "Orders..."
it then sets the orders state or appropriate variable such as sneak_state.

one common game design pattern seen in this code:
populating a menu by iterating though one or more lists and selecting some, but not all the entries, based on some criteria.
and then, once you get the menu pick, you iterate through the lists again to figure out which one it was.

another classic game design pattern:
B2_BM_badguys_closest2loc()
this is the classic best match search algo used in many places and many ways in many games.
you iterate through a list, finding the entry with the best value of some sort, such as its closest, does the most damage, etc.
you start by setting the best found value to some very bad value, and the best found entry to none. as you go though the list, if the current entry's value is better, it becomes the new best found value, and the entry becomes the new best found entry. when you're done, you return the best found entry, and perhaps the best found value as well, such as closest target, and its range.

This code also includes a simple quick and dirty raypick routine. It starts at the eye point, and uses a unit vector in the lookat direction. it repeatedly adds the unit vector to the point, then checks for intersection with the ground: point.y <= heightmap_of(point.x,point.z), or range > 200 (in case they aren't aiming at the ground at all!). If it gets an intersection with the ground, it returns the x,z location of the intersection.

You'll notice that in both this code and the AI code, many things need to be done twice, once for PCs, IE bandmembers (cm[]), and once for npcs/monsters (animal[]). Caveman has multiple PCs, like a household in The Sims. The player can tab between them at any time to manually control any bandmember they want. Other bandmembers are controlled by the new AI. So the game has two basic types of entities: bandmembers, and everything else. and it therefore has two entity lists, and more or less duplicate code for each of the two basic type of entites. A C-E approach that used structs for the components common to both could use a single set of code to operate on the structs, irreguardless of whether they belonged to a bandmember or something else. The game uses this approach with requirements structs used by actions, objects, and skills, and with inventory structs used by both bandmembers and everyone else.
// ---------------------------- NEW ORDERS API --------------------------------------------- // returns:// *who = -1 on cancel, is_BM is undefined.// *who = -2 for everyone, is_BM is undefined.// else returns who and is_BMvoid B2_order_who_menu(int cm1,int *who,int *is_BM){// n is npc #. c is number of menu options. b is who to issue orders to. a is loop counter char s[100]; int n,a,b,c; Znewmenu("Issue orders to..."); Zaddmenu("Everyone"); c=1; for (a=0; a 300) { continue; } Zaddmenu(cm[a].name); c++; } for (a=0; a 300) { continue; } if (animal[a].state == WARRIOR) { strcpy_s(s,100,"Warrior "); n=animal[a].butchered; strcat_s(s,100,npc[n].name); Zaddmenu(s); c++; } else if (animal[a].state == COMPANION) { strcpy_s(s,100,"Companion "); n=animal[a].butchered; strcat_s(s,100,npc[n].name); Zaddmenu(s); c++; } else if (animal[a].state == TAMED) { strcpy_s(s,100,"Pet "); strcat_s(s,100,animaltype[animal[a].type].name); Zaddmenu(s); c++; } } Zaddmenu("Cancel"); c++; b=menu(c); if (b == c) {// cancel *who=-1; return; } if (b == 1) {// everyone *who=-2; return; } c=1; for (a=0; a 300) { continue; } c++; if (c == b) { *who=a; *is_BM=1; return; } } for (a=0; a 300) { continue; } if (animal[a].state == WARRIOR) { c++; if (c == b) { *who=a; *is_BM=0; return; } } else if (animal[a].state == COMPANION) { c++; if (c == b) { *who=a; *is_BM=0; return; } } else if (animal[a].state == TAMED) { c++; if (c == b) { *who=a; *is_BM=0; return; } } }// should never get here - return cancel just in case *who=-1; } int B2_orders_menu(){Znewmenu("Orders...");Zaddmenu("Wait here");Zaddmenu("Follow...");Zaddmenu("Goto...");Zaddmenu("Target...");Zaddmenu("Attack");Zaddmenu("Sneak");Zaddmenu("Stop sneaking");Zaddmenu("Maintain distance");Zaddmenu("Run away!");Zaddmenu("As you were");Zaddmenu("Cancel");return(menu(11));} // returns who = -1 on cancel, else returns who and is_BMvoid B2_follow_who_menu(int cm1,int *who,int *is_BM){// c is number of menu options. b is menu result. a is loop counter. n is npc #.char s[100];int a,b,c,n;Znewmenu("Follow...");c=0;for (a=0; a 300) { continue; } Zaddmenu(cm[a].name); c++; }for (a=0; a 300) { continue; } if (animal[a].state == WARRIOR) { strcpy_s(s,100,"Warrior "); n=animal[a].butchered; strcat_s(s,100,npc[n].name); Zaddmenu(s); c++; } else if (animal[a].state == COMPANION) { strcpy_s(s,100,"Companion "); n=animal[a].butchered; strcat_s(s,100,npc[n].name); Zaddmenu(s); c++; } else if (animal[a].state == TAMED) { strcpy_s(s,100,"Pet "); strcat_s(s,100,animaltype[animal[a].type].name); Zaddmenu(s); c++; } }Zaddmenu("Cancel");c++;b=menu(c);if (b == c) {// cancel *who=-1; return; }c=0;for (a=0; a 300) { continue; } c++; if (c == b) { *who=a; *is_BM=1; return; } }for (a=0; a 300) { continue; } if (animal[a].state == WARRIOR) { c++; if (c == b) { *who=a; *is_BM=0; return; } } else if (animal[a].state == COMPANION) { c++; if (c == b) { *who=a; *is_BM=0; return; } } else if (animal[a].state == TAMED) { c++; if (c == b) { *who=a; *is_BM=0; return; } } }// should never get here, return cancel just in case*who=-1;} void B2_BM_orders_follow(int giver,int cm1){int who,is_BM;B2_follow_who_menu(giver,&who,&is_BM);if (who == -1) {// cancel return; }cm[cm1].orders=B2_ORDERS_FOLLOW;cm[cm1].ordersdata[0]=who;cm[cm1].ordersdata[1]=is_BM;} void B2_animal_orders_follow(int giver,int cm1){// cm1 is the animal #int who,is_BM;B2_follow_who_menu(giver,&who,&is_BM);if (who == -1) {// cancel return; }animal[cm1].orders=B2_ORDERS_FOLLOW;animal[cm1].ordersdata[0]=who;animal[cm1].ordersdata[1]=is_BM;} void B2_cast_ray(int cm1,int *success,location *L){// casts ray from eyepoint of cm1, if ray goes past 200, success=0, else success=1 and sets L.x,L.z// point=eyepoint of cm1// dir=fwd dir (unit vector) of cm1int quit;D3DXVECTOR3 point;D3DXVECTOR4 dir;point.x=0.0f;point.y=0.0f;point.z=1.0f;Mstart();MrotateRADS(0,cm[cm1].xr);MrotateRADS(1,cm[cm1].yr);D3DXVec3Transform(&dir,&point,&Mmat);point.x=cm[cm1].x;point.z=cm[cm1].z;point.y=heightmap(cm[cm1].mx,cm[cm1].mz,cm[cm1].x,cm[cm1].z);if (cm[cm1].sneak_state == 0) { point.y+=5.0f; }else { point.y+=3.0f; }quit=0;while (!quit) { if (BBdist((int)cm[cm1].x,(int)cm[cm1].z,(int)point.x,(int)point.z) > 200) { *success=0; quit=1; } else if (point.y <= heightmap(cm[cm1].mx,cm[cm1].mz,point.x,point.z)) { *success=1; L->x=point.x; L->z=point.z; quit=1; } else { point.x+=dir.x; point.y+=dir.y; point.z+=dir.z; } }} /*do { drawscreen if L-click cast ray if ray > 200 continue L->x=ray.x; L->z=ray.z; return; while 1 */ void B2_raypick_loc(int cm1,char *s,location *L){ int quit,success,x,y,b; quit=0; while (!quit) { drawscreen(0); Zbeginsprite(); tx(10,10,s); Zendsprite(); showscene(); B2_mouse_turn_camera(cm1); Zdomessages(); getmouse(&x,&y,&b); if (b != 1) { continue; } B2_cast_ray(cm1,&success,L); if (success == 1) { quit=1; } } nobutton(); } int B2_BM_badguy_closest2loc(location *L){int a,best,bestrng,rng;best=-1;bestrng=100000;for (a=0; ax,(int)L->z); if (rng < bestrng) { bestrng=rng; best=a; } }return(best);} int B2_raypick_tgt(int cm1){location L;int tgt;B2_raypick_loc(cm1,"Select target...",&L);tgt=B2_BM_badguy_closest2loc(&L);return(tgt);} void B2_BM_orders_goto(int giver,int cm1){location L;B2_raypick_loc(giver,"Go to...",&L);cm[cm1].orders=B2_ORDERS_GOTO;cm[cm1].ordersdata[0]=(int)L.x;cm[cm1].ordersdata[1]=(int)L.z;} void B2_animal_orders_goto(int giver,int cm1){// cm1 is the animal #location L;B2_raypick_loc(giver,"Go to...",&L);animal[cm1].orders=B2_ORDERS_GOTO;animal[cm1].ordersdata[0]=(int)L.x;animal[cm1].ordersdata[1]=(int)L.z;} void B2_BM_orders_target(int giver,int cm1){int tgt;tgt=B2_raypick_tgt(giver);cm[cm1].orders_tgt=tgt;cm[cm1].has_orders_tgt=1;} void B2_animal_orders_target(int giver,int cm1){// cm1 is the animal #int tgt;tgt=B2_raypick_tgt(giver);animal[cm1].orders_tgt=tgt;animal[cm1].has_orders_tgt=1;} void B2_issue_BM_orders(int giver,int cm1,int orders){switch (orders) { case 1:// wait here cm[cm1].orders=B2_ORDERS_GOTO; cm[cm1].ordersdata[0]=(int)cm[cm1].x; cm[cm1].ordersdata[1]=(int)cm[cm1].z; break; case 2:// follow... B2_BM_orders_follow(giver,cm1); break; case 3:// goto... B2_BM_orders_goto(giver,cm1); break; case 4:// target... B2_BM_orders_target(giver,cm1); break; case 5:// attack! cm[cm1].orders=B2_ORDERS_ATTACK; break; case 6:// sneak if (cm[cm1].sneak_state == 0) { cm[cm1].sneak_state=1; } break; case 7:// stop sneak cm[cm1].sneak_state=0; break; case 8:// maintain dist cm[cm1].orders=B2_ORDERS_MAINTAIN_DIST; break; case 9:// run away! cm[cm1].orders=B2_ORDERS_FLEE; break; case 10:// as you were - clears orders cm[cm1].orders=B2_ORDERS_NONE; break; }} void B2_issue_animal_orders(int giver,int cm1,int orders){// cm1 is the animal #switch (orders) { case 1:// wait here animal[cm1].orders=B2_ORDERS_GOTO; animal[cm1].ordersdata[0]=(int)cm[cm1].x; animal[cm1].ordersdata[1]=(int)cm[cm1].z; break; case 2:// follow... B2_animal_orders_follow(giver,cm1); break; case 3:// goto... B2_animal_orders_goto(giver,cm1); break; case 4:// target... B2_animal_orders_target(giver,cm1); break; case 5:// attack! animal[cm1].orders=B2_ORDERS_ATTACK; break; case 6:// sneak if (animal[cm1].sneak_state == 0) { animal[cm1].sneak_state=1; } break; case 7:// stop sneak animal[cm1].sneak_state=0; break; case 8:// maintain dist animal[cm1].orders=B2_ORDERS_MAINTAIN_DIST; break; case 9:// run away! animal[cm1].orders=B2_ORDERS_FLEE; break; case 10:// as you were - clears orders animal[cm1].orders=B2_ORDERS_NONE; break; }} void B2_order_everyone(int cm1,int orders){// a is loop counterint a;for (a=0; a 300) { continue; } B2_issue_BM_orders(cm1,a,orders); }for (a=0; a 300) { continue; } if (animal[a].state == WARRIOR) { B2_issue_animal_orders(cm1,a,orders); } else if (animal[a].state == COMPANION) { B2_issue_animal_orders(cm1,a,orders); } else if (animal[a].state == TAMED) { B2_issue_animal_orders(cm1,a,orders); } }} void B2_order_single(int cm1,int who,int is_BM,int orders){if (is_BM) { B2_issue_BM_orders(cm1,who,orders); }else { B2_issue_animal_orders(cm1,who,orders); }} void B2_issue_orders(int cm1){int who,orders,is_BM;B2_order_who_menu(cm1,&who,&is_BM);if (who == -1) {// cancel return; }orders=B2_orders_menu();if (orders == 11) {// cancel return; }if (who == -2) { B2_order_everyone(cm1,orders); }else { B2_order_single(cm1,who,is_BM,orders); }}
Norman Barrows
New land animal AI implemented



The new land animal AI has been implemented and plugged into the game.




The new land animal AI actually consists of 8 different types of AI:
1. AI for NPC allies of the player (travelling companions, hired warriors, and tamed pets).
2. AI for thieves
3. AI for neutral NPC cavemen
4. AI for animals and hostile cavemen who are defending a location
5. AI for hostile cavemen
6. AI for predators
7. AI for animals that defend their ground
8. AI for animals that maintain their distance






In refactoring the ground animal AI, it has taken a new form.

The old AI was more or less a setstate / runstate type design.

The new AI is more of the form:

[color=#0000ff]if (condition A) handle_condition_A()
else if (condition B) handle_condition_B()
else if (condition C) handle_condition_C()
else if (condition D) handle_condition_D()
etc.[/color]

The routines to handle different conditions turn and move the animal,trigger attacks, and play animations as required.






The conditions are more or less ordered from most to least important to respond to.

For example, all the animal AI code starts out the same:
if fatigued, restelse if rested, stop restingelse if cornered, attackelse if in collision recovery, do collision recoveryelse if taking fire { if have target, attack else flee in random direction }else if halfdead, fleeelse if have target within 20 feet { if target is stronger, flee else attack }







Instead of a single AI_state variable as used in a setstate / runstate design,
the new AI instead uses multiple state variables:

is_resting (boolean)
is_hunting (boolean)
in_collision_recovery (boolean)
orders (a predefined order such as goto location).
has_orders_target (boolean)
graze_state (stand, wander, flock, or migrate)
taking_fire (boolean)

this allows an animal to be in multiple AI states simultaneously, such as hunting, taking fire, in collision recovery, and resting.

IE, i'm on the hunt (targeting animals not bigger than me), but i'm taking fire from an unknown source, so i'm running,
but i've hit an obsticle, so i'm in collision recovery (bang and turn), and while in collision recovery, my fatigue has maxed out,
and i can't take another step, so i have to stop and catch my breath for a moment (rest).





One interesting thing is the fact that the handle_condition code for grazing actually uses a state machine variable (graze_state)
and uses a setstate / runstate type design.




I've been coding game AI on and off for 25 years now, and i keep coming back to expert systems implemented as multilevel decision trees
with multiple state variables, and the odd state machine thrown in at some level or another. Basically, each of the handle_condition
routines uses whatever AI type will work best for its particular task. In the case of graze, this just happens to be a state machine.
Most of the other handle_condition routines are decision trees, or simply code to turn some amount, move some amount, and possibly attack,
based on the condition being handled. At the lowest level, all the higher level AI routines eventually output calls to move, turn, attack,
and play animations as appropriate.




The dreaded avian AI is next! It turns out that AI for an avian predator is quite complex - perhaps amongst the most complex single entity AI I've ever heard of. And its ALMOST right, but not quite there. They still do this circle the target thing from time to time that doesn't seem correct, and i'm not quite sure what they're doing (as far as what their AI state is). Putting a simple trace variable on it will reveal what's going on, I just haven't gotten around to it yet. But implementing and testing the new avian AI should finally fix that odd behavior. I suspect it may have something to do with either resting but not landing first, or trying to fly and turn towards a target who is trying to move and turn towards you, resulting in the two of you moving in a circle, chasing each other.
Norman Barrows
New bandmember AI done

The new AI for bandmembers is done. The new orders menu is done as well. The new AI can deal with being in a shelter, taking fire, being cornered, etc, as well as follow orders or do actions. It now automatically favors missile weapons and skirmish mode before hand weapons and melee mode. Only thing left is to make it use sprint speed, and play the correct animations (sneak, walk, run, etc). right now it doesn't do sneak or sprint animations, as those are new AI capabilities. a simple fix, if sneaking, animation=sneak, else if walking, animation=walk, etc.

It almost worked perfectly the first time. bandmember_is_cornered needed to take into account being inside a temporary shelter. it simply does a check for something_in_way_of_location 4 feet north, south, east, and west of the bandmember. but when they're inside a temp shelter with a BBox radius of 5 or 10, that doesn't quite work so well.... i had to make it check for badguys and leave a shelter before doing common AI such as cornered, collision, recovery, taking fire, nearby threat, etc - not after doing the common AI. i also had to make it leave a shelter before it tried to follow orders when there were no badguys around. and i forgot to call mouse_turn_camera in the raypick code for goto and designate target orders. the new AI code is about 2000 lines of source. the new orders menu code, including raypick code, is about 700 lines of source.

i plan to post some code so people can see how it turned out. basically its as it appears in the pseudocode in the previous journal entry. at the lower levels, some of the existing low level AI routines were suitable for reuse with no alterations, so they were used.
Norman Barrows
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.
Norman Barrows
New Avian AI done

The new Avian AI has been implemented.

There are just a few remaining tasks to complete, and then the AI will be refactored, with the new orders features added.

first off, i still have to hook up the avian ai:


the current code:
you can see how avians were originally implemented as a bunch of special cases throughout the animal AI code.
you can also see where the old land animal AI has been turned off, and the new land animal AI gets called instead.
All three versions of the AI are still in the code: the original AI, the setstate/runstate refactored AI, and the new AI.
Most of the old AI code is turned off via comment blocks. function level linking takes care of the rest.


void move_animals(){int a,t;if (turns_per_render > 128) return;if (last_active_animal < 0) return;for (a=0; a <= last_active_animal; a++) { if (!animal[a].active) continue; if (!animal[a].alive) continue; t=animal[a].type; if (animaltype[t].avian) { if (animal[a].suprised) { move_suprised_avian(a); continue; } else if (animaltype[t].AI==ATTACK_AI) avian_predator_setstate(a); else setstate(a); runstate(a); } else { if (animal[a].suprised) continue; // setstate(a); // runstate(a); B2_run_animal_AI(a); } }}



the new code will look more like:


void move_animals(){int a,t;if (turns_per_render > 128) return;if (last_active_animal < 0) return;for (a=0; a <= last_active_animal; a++) { if (!animal[a].active) continue; if (!animal[a].alive) continue; t=animal[a].type; if (animaltype[t].avian) B2_run_avian_AI(a); else { if (animal[a].suprised) continue; B2_run_animal_AI(a); } }}



I also need to add fatigue modeling to the animal and avian movement code in the new AI.
Here's a snippet of code from the old AI that does fatigue modeling and sets the movement amount all at once.
This will be adapted to create a routine that increases fatigue based on the animal's current movement rate.
This "increase_fatigue" routine will then be called by the land animal and avian move routines.



if (animal[a].fatigue>=100000) { animal[a].fatigue-=7; return; }t=animal[a].type;spd=animal_speed(a);if (running) { if (animal[a].fatigue<100000) { spd*=4.0f; animal[a].fatigue+=64; if (animal[a].fatigue>100000) animal[a].fatigue=100000; } else { spd *= 2.0f; animal[a].fatigue+=4; if (animal[a].fatigue>100000) animal[a].fatigue=100000; } }else { animal[a].fatigue+=2; if (animal[a].fatigue>100000) animal[a].fatigue=100000; }



i also need to add modeling of sneak detection for hired NPC warriors and NPC travelling companions. while they are NPCs,
they are friendly with the player, and the player can give them combat orders. One of the orders they can be given is to
use sneak mode. So i need to make a version of the sneak detection code that works with animals. Cavemen are just another
animal type (species), type 2 to be specific: homo sapien. Hippidion is type 0, smilodon is type 1. pterasaur is type 3,
in anticipation of the bring on the dinos option. Yes, you can punch up a pterasaur encounter and tkae them on with a
spear and throwing rocks! . But for now, they can only be triggered from the playtest menu, and are not in the game's
encounter tables.

The current code only works for bandmembers. its that same old design question we all face when starting a game project: should
i make the player part of the entity list or keep them separate? keeping them in the list means possible code reuse (a good thing).
but usually you need much more data about the player then other entities. that leads to designs where some data is in the entity
list, and the rest is elsewhere. somewhat more complex (a bad thing). a lot depends on whether you can get code savings.
A C-E apprach for components shared by both player and non-player entities seesm like a good idea, but can also be a bit
more complex than a brute force implementation. In my Airships simulator, i model the players ship in great detail, then copy
the pertinant results to entity #0 which is predfeined to be the player's ship. This gives me a simple player airship data
structure, but can still use generic entity code for things like movement, collisions, drawing, etc.

In Caveman, there's isnt a single PC like in most single player games. The human player can control up to 10 PCs. So Caveman
has two entity lists: one for PCs, and one for eveything else. The data for a PC is much more than that for an animal
(everything else). so they are two different types of data strutres stored in two separate lists. Right now, the sneak code work
with a cavemanrec, but not with an animalrec. note that this code is actually in CScript, a c++ macro processor/code generator
i wrote and use in-house. You can freely mix c++ and Cscript code. the first line of BMmodel_sneak_detection() reads:
function void BMmodel_sneak_detection int a
the . at the end is a close squiggly.
This code includes raypick /raycasting style line-of-sight testing.
Note that Caveman differentiates between detected by friendly and detected by hostile. That way you're not detected just becasue
your horse can see you (Oblivion!).


int detection_range(int a) // returns dist at which player a can be seen{int x,z;x=cm[a].mx;z=cm[a].mz;switch(map[x][z].coverage) { case WOODS: case GRASS: case JUNGLE: return(50); }return(300);} // returns 1 if there's a clear line-of-sight from location L to loction L2int clear_LOS(location *L,location *L2){float dx,dy,dz, // displacement & normalized direction vector from p1 to p2 x,y,z, // the point we raycast from p1 to p2 mag; // magnitude of displacement vector from p1 to p2 int mx,mz, // mx,mz of the point we raycast from p1 to p2 a; // loop counter. the number of steps iterated so far in the raycast.location L3; // location of our raycast point for call to terrain_in_way()x = L2->x + (L2->mx - L->mx) * 26400.0f; // convert x,z of p2 to p1 mx,mz relative coordsz = L2->z + (L2->mz - L->mz) * 26400.0f; dx = x - L->x; // calc displacement vector dx,dy,dz between the 2 pointsdy = L2->y - L->y;dz = z - L->z;mag=sqrt(dx*dx+dz*dz); // calc magnitude of the displacement vectormag=sqrt(mag*mag+dy*dy);dx/=mag; // divide displacment vector by mag to get normalized direction vectordy/=mag;dz/=mag;mx=L->mx; // start at 1st pointmz=L->mz;x=L->x; y=L->y;z=L->z;for (a=0; a < (int)mag; a++) // dir vec mag = 1. # steps = mag of displacement vec. { x+=dx; // move the point y+=dy; z+=dz; if (x>26400.0f) // handle map sq edges { x-=26400.0f; mx++; } if (x<0.0f) { x+=26400.0f; mx--; } if (z>26400.0f) { z-=26400.0f; mz++; } if (z<0.0f) { z+=26400.0f; mz--; } L3.mx=mx; // check for terrain collision L3.mz=mz; L3.x=x; L3.y=y; L3.z=z; if (terrain_in_way(&L3)) return(0); if (heightmap(mx,mz,x,z) >= y) return(0); // check for ground collision }return(1); // no collisions, have clear LOS, return 1.} // returns: 0=not detected. 1=detected by some animal. 2=detected by wild or hostile targeting the playerint detection_state(int a) // a is band member #{int b, // animal # dr, // detection rng of bandmember a rh, // rel heading, animal to band member rng, // range from band member to animal result, // return value targeting_player; // booleanfloat x,z;location L,L2;dr=detection_range(a);result=0;for (b=0; b.active) continue; if (! animal.alive) continue; if (animal.location != cm[cm0].location) continue; if (abs(cm[a].mx-animal.mx) > 1) continue; // more than 1 map sq away in x direction if (abs(cm[a].mz-animal.mz) > 1) continue; // more than 1 map sq away in z direction if ( ((animal.state==WILD) || (animal.state==HOSTILE)) && (animal.tgt_is_caveman==1) && (animal.tgt==a)) targeting_player=1; else targeting_player=0; if ( (result==0) || ((result==1) && (targeting_player)) ) { x=cm[a].x+(cm[a].mx-animal.mx)*26400.0f; // calc band member x,z rel to animal's map sq z=cm[a].z+(cm[a].mz-animal.mz)*26400.0f; // calc band member x,z rel to animal's map sq rng=rng2D((int)animal.x,(int)animal.z,(int)x,(int)z); if (rng > animal_cliprng-20) continue; if (rng > dr) continue; rh=relheading((int)animal.x,(int)animal.z,(int)rad2deg(animal.yr),(int)x,(int)z); // normalized, ok. if (abs(rh) > 90) continue; L.mx=animal.mx; L.mz=animal.mz; L.x=animal.x; L.y=animal.y; L.z=animal.z; L2.mx=cm[a].mx; L2.mz=cm[a].mz; L2.x=cm[a].x; L2.y=cm[a].y; L2.z=cm[a].z; if (! clear_LOS(&L,&L2) ) continue; } else continue; switch(result) { case 0: if (targeting_player) return(2); else result=1; break; case 1: return(2); } }return(result);} // returns 1 if band member a is targeted by a hostile caveman or wild animalint bandmember_targeted(int a){int b;for (b=0; b.active) continue; if (! animal.alive) continue; if ((animal.state != HOSTILE) && (animal.state != WILD)) continue; if (! animal.tgt_is_caveman) continue; if (animal.tgt != a) continue; return(1); }return(0);} fn v BMmodel_sneak_detection i aif (a % 15 != frame) return;if (cm[a].sneak_state==0) return;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; } }.





another thing left to do is to init the "taking fire" variables for animals when they get hit by a missile.
again, there's only code for bandmembers at the moment. More CScript. this function just sets three variables.
It gets called when a bandmember gets hit by a missile. I need to make a version for animals. IE: replace cm[] with
animal[], then call it from the code that checks missile collisions with animals.


fn v set_BM_taking_fire i cm1= cm[cm1].taking_fire 1= cm[cm1].taking_fire_counter 0= cm[cm1].taking_fire_dir (float)(dice(628)/100.0f).



another thing to do is make an avian version of do_orders, where the new AI follows the player's orders. This
is basically a copy / paste /edit of B2_do_orders to create B2_avian_do_orders. calls to B2_avian_setpitch get
added after calls to turn the animal, and avian versions of attack and move are used.


the last thing left is to add animal[a].diving to the savegame file format. Its used by avian predators to track
their attack state (diving on tgt, or climbing for next pass). the variable has already been added to the
animalrec struct declaration and is used by the code. but the vsae and load code writes individual fields, not
entire structs. so the old savegames will still load, with animal[a].diving taking a default value of zero (false)
as desired. So all i have to do is add one line of code to save the variable, then run the game to convert the
existing savegames to the new format by simply loading and saving each one (there's only one - a long term
playtest game). Then i add one line of code to read the new variable, and i'm done. file format updated, and
savegames converted. If i wanted backward compatability with savegames i didn't convert, i add an extra line of
code to save a junk value before the new data. when i read, i try to tread the junk value. if i get an EOF, its
an old format, and any variables normally read after that point will not be read and simpy keep their default
initial values. if i don't get an eof, there's more data, so i keep reading until the next "EOF test" or i've
read everything. Since changes to the file format are appended at the end of the file, you can read in something
many versions old and get the original data plus defualt values for newver variables. So the basic idea is you
init everything to default values, then read in the savegame values over them. whatever you don't read in,
uses the default values. when you save, you write everyting.
Norman Barrows

Issuing orders to party members and refactoring AI...



NOTES:
1. in this article, any distances mentioned are in feet.
2. abbreviations used in the pseudocode:
"ret" = return
"dir" = direction
"loc" = location
"rh" = relative heading
"CR" = collision recovery

I've been playtesting the game at advanced stages, when you can control more than one band member. This means its time to implement giving orders to party members. The goal is to get the orders and AI to the point where the player, in control of one PC, and able to issue orders to some number of friendly NPCs, can stage an ambush of a target. This requires abilities like use stealth mode, goto location, use missile or hand weapons, attack on my signal, etc. After thinking about it for a while, and going through two design iterations i came up with:
.-----------------------------------------------------------Orders design, version 2.player presses orders hotkey ( default = 'O' )Give orders to...==============EveryoneParty member 1Party member 2Party member 3...Cancellist of party members is all band members, pets, warriors, and companions within 300 of player.Orders...=================goto - location (raypick)use stealth modeuse non-stealth modeuse hand wpn (and melee AI)use missile wpn (and skirmish AI)non-stealth attack (designated or default target)stealth attack (designated or default target)follow - someone (including player - pick list of party members)as you were - cancels ordersrun awaydesignate target (raypick)maintain distancewait here (goto location, location=here)Cancelraypick location: cast ray. location is where ray hits ground: IE when ray.y <= heightmap(ray.x,ray.z).raypick tgt: raypick location, then return closest badguy to that location.Follow.....==============Party member 1Party member 2Party member 3...Cancellist of party members is all band members, pets, warriors, and companions within 300 of player.--------------------------------------------
.
.
.

The original plan was to modify the AI as follows:
.if unit has orders, do orders // (perhaps with some special cases, like there is a badguy about to attack me)else do normal AI
.
.
.
So then i took a look at what the "normal" AI was. The normal AI was the original quick and dirty prototype AI, which has been modified and expanded until it actually handled over a dozen different types of AI. Its original form was:
.for each unit set target steer move attack
.
.
.
and inside set target, steer, move, and attack, it dealt with the many flavors of AI. On top of this, a setstate/runstate design had been placed, that set the state based on AI type, then called runstate, which made the actual calls to steer, move, attack, etc. Plastering orders on top of all this, and handling the special cases for orders sounded like i would be adding more bad design to existing bad design. Refactoring was called for. This was actually planned all along. the idea was to start with some quick and dirty AI, modify it as needed so its design evolved during development, and then refactor once its desired behavior had been pretty well determined. When i took a look at the exiting AI, i copied it (in pseudocode) into the todo list for the project, so i could get a better handle on what was there. Here's the basics of what i found:
.====== current AI =================================== Bandmember AI---------------------if badguys_near, if inshelter, leave shelter else if doing action, stop actionif not doing action setwpn if in collision recovery, do collision recovery and return set tgt steer move atkelse do action animal AI:----------------setstaterunstate setstate: ----------------if suprised avian, move suprised avian. state=stand, then calls runstate and returnselse if avian predator, avian predator setstateelse if resting, and not done resting, state=restelse if fatuigued, state=restif caveman, setwpnif running thief, thief run setstateelse if approaching thief, thief approach setstateelse if taking fire, taking fire setstateelse if defend location, defend location setstateelse if friendly, friendly setstateelse if hostile, hostile setsatateelse if companion, wartrior, or tamed: companion setstateelse if attack_AI, attack setstate (predators)else if maintain_distance_AI, maintain distance setstateelse if defend_AI, defend setstateelse all others setstate avian predator setstate:---------------------------------set tgt or rng&rhif halfdead, state=runawayelse if taking fire, taking fire setstateelse if resting, and not done resting, state=restelse if fatuigued, state=restelse if not attacking and has tgt within 20, save current state, state=attackelse if attacking but no tgt within 20, state=savestateelse if state=predator hunt if carcass nearby, state= eat kill else if no tgt, state=flockelse if state=predator eat if carcass nearby, state= eat kill else if no tgt, state=flockelse (grazing) if make check, state=predator hunt (time to go hunting!) thief run setstate----------------------if cornered, state=corneredelse if state=collision recovery, do nothingelse set tgt or rng state= thief run thief approach setstate----------------------if cornered, state=corneredelse if state=collision recovery, do nothingelse set tgt or rng state= thief approach taking fire setstate-------------------------if cornered, state=corneredelse if state=collision recovery, do nothingelse set tgt or rng if animal is leaving, do nothing else if tgt is bandmember up a tree and make check, leave else if has tgt if halfdead, state=runaway else state=attackelse state=flock defend location setstate--------------------------------if cornered, state=corneredelse if state=collision recovery, do nothingelse set tgt or rng state=attack tgt closest to location friendly setstate----------------------------if cornered, state=corneredelse if state=collision recovery, do nothingelse set tgt or rng if talking to bandmember, state=stand else if CRH caveman (a caveman at a shelter) if within 500 of home if >50 to home, moveto home else if has tgt <50 from home, attack else graze else if has tgt, attack else graze hostile setstate-----------------------if cornered, state=corneredelse if state=collision recovery, do nothingelse set tgt or rng state=attack companion setstate--------------------------if cornered, state=corneredelse if state=collision recovery, state=collision recovery (no change)else set tgt or rng if > 100 from ower,state= moveto owner else if has tgt, state=attack else if talking to bandmember, state=stand else if > 20 to owner, state=moveto owner else state=stand attack setstate (predators)--------------------------------------if cornered, state=corneredelse if state=collision recovery, state=collision recovery (no change)else set tgt or rng if animal is leaving, do nothing else if tgt is bandmember up a tree and make check, leave else if state=attack if has tgt if halfdead, state=runaway else if tgt rng >20 flock else (no tgt) state=flock else if state=predator hunt if has tgt if halfdead, state=runaway else if carcass nearby, state=eat kill else (no tgt) if halfdead, state=runaway else if carcass nearby, state=eat kill else state=flock else if state=eat if has tgt if halfdead, state=runaway else if rng<20 state=attack else (no tgt within 20) if carcass nearby, state=eat kill else state=flock else (no tgt) if carcass nearby, state=eat kill else state=flock else if has tgt if halfdead, state=run away else if tgt <20 state=attack else graze else graze if make check, state=predator hunt maintain distance setstate----------------------------------if cornered, state=corneredelse if state=collision recovery, state=collision recovery (no change)else set tgt or rng if halfdead, state=run away else graze defend setstate---------------------- if cornered, state=corneredelse if state=collision recovery, state=collision recovery (no change)else set tgt or rng if has tgt if halfdead, state=run away else if tgt<20, state=attack else graze else graze all others setstate-------------------------if cornered, state=corneredelse if state=collision recovery, state=collision recovery (no change)else set tgt or rng if tgt is bandmember up a tree and make check, state=leave if state=leave, do nothing else state=attack graze---------state periodically transitions between stand, wander, and flock, at random. set tgt--------------------if caveman if thief, tgt=current bandmember else if defend location, set hostile tgt defloc else if hostile, set hostile tgt else if freindly, set friendfdly tgt else if warrior or companion, set tamed tgtelse (animal) if defend location, set wild tgt defloc if wild if state=predator hunt, set predator tgt else set wild tgt else if subdued or captured, set wild tgt else if tamed, set tamed target set hotile tgt defend location--------------------------------------targets the detected bandmember, non hostile animal, not subdued animal, or not captured animal which is closest to the location to defend. set hostile tgt--------------------targets the closest:detected bandmember, not obscured by coverage due to range- or -non hostile, non subdued, non captured animal, not obscured by coverage due to range set friendly tgt--------------------targets closest:wild or hostile, not obscured by coverage due to range set tamed tgt-----------------------if rng to owner > 20, tgt=ownerelse tgt=set friendly tgt (closest wild or hostile, not obscured by coverage diue to range)if tgt rng to owner > 20, tgt= none set wild tgt defend location-----------------------------------targets closest to defend location:detected bandmember-or-not subdued or captured animal, not of same type as attacker. set wild target----------------------targets closest:detected bandmember, not obscured by coverage due to range-or-not subdued or captured animal, not of same type as attacker, not obscured by coverage due to range set predator tgt---------------------targets closest:detected bandmember, not obscured by coverage due to range, if a baseline caveman has same or less HP than attacker (predators dont target animal types with more hit points than the predator has)-or-not subdued or captured animal, not of same type as attacker, not obscured by coverage due to range, and has same or less HP than attacker (predators dont target animal types with more hit points than the predator has). runstate---------------if in collision recovery move_collision_recovery return stand: if avian if airborne, land else recover fatigueelse recover fatigue wander: (a "wander to" location is randomly generated and stored when this state is first entered)if avian, calc rel heading to wander to locationsteer_animal3moverun run away:steer_animal3moverundo_animal_atk attack, predator hunt:if avian, run avian attack AIelse calc heading set rng steer animal3 new_moveatk do_animal_atk eatkill:do eat kill cornered:do_cornered def loc:do def loc moveto home:do move to home friendly atk:calc headingset rngnew steer animalnew move animaldo animal atk thief approach:calc headingset rngnew steer animalnew move animalif rng<10 thief dialog (gimme all your stuff, or else!) thief run:steer animal3moverun rest:if airborne avian, landelse recover fatigue move to owner:set tgtcalc headingset rngnew steer animalnew move animal move_collision_recovery-----------------------------------calc amount to turn, and turn animalif animal facing generaly in desired direction, moverun increment collision recovery counter if counter >= limit, end collision recovery land----------move animalif alt<=0, alt=0 (they landed)else adjust angle of dive based on alt steer_animal3 - uses trinkets taken as desired heading---------------------------if avian if runaway, add 180 to desired heading turn_animal set pitch to climb else calc rng if behind and rng<50, do nothing (fly straight) else turn_animal climb_or_diveelse if runaway, add 180 to desired heading calc RelHeading turn based on R.H. moverun------------------set speed based on walking, running or sprintingcalc new locationif collision go into collision recoveryelse update location ========== end current AI =================
.
.
.
the original plan for refactoring was:
at the highest level, i wanted two routines: setstate and runstate.
at the lowest level, i also wanted two routines: turn(amount) and move(amount).
Great! Then it was just the stuff in between that i had to worry about.

So i pseudocoded a setstate/runstate version of the AI, improving the designed behavior at the same time. This wasn't that hard. setstate simply called a custom setstate routine for each of the types of AI, and runstate was largely unchanged. I also separated the avian from the non-avian runstates, as they do differ somewhat. I called the new AI api the "BB " api, hence the BB at the beginning of function names and predefined values.

Here's the result:
.int BBsetstate_common (returns state selected, or -1 for none selected)=========================if cornered, ret BB_ATTACKelse if in collision recovery, ret BB_COLLISION_RECOVERYelse if halfdead, ret BB_FLEEelse if badguy within 20 if badguy stronger, ret BB_FLEE else ret BB_ATTACKelse if taking fire if have tgt, ret BB_ATTACK else RET BB_FLOCKelse if resting ret BB_STANDelse ret -1 int BBsetstate_avian_common (returns state selected, or -1 for none selected)=========================if cornered, ret BB_AVIAN_ATTACKelse if in collision recovery, ret BB_AVIAN_COLLISION_RECOVERYelse if halfdead, ret BB_AVIAN_FLEEelse if badguy within 20 if badguy stronger, ret BB_AVIAN_FLEE else ret BB_AVIAN_ATTACKelse if taking fire if have tgt, ret BB_AVIAN_ATTACK else RET BB_AVIAN_FLOCKelse if resting ret BB_AVIAN_STANDelse ret -1 int BBsetstate_orders=============================set state based on the unit's orders int BBsetstate_avian_orders=============================set state based on the avian unit's orders int BBsetstate_graze=======================as current graze, adds migrate.outputs:BB_STANDBB_WANDERBB_FLOCKBB_MIGRATE int BBsetstate_avian_graze=======================as current graze, adds migrate.outputs:BB_AVIAN_STANDBB_AVIAN_WANDERBB_AVIAN_FLOCKBB_AVIAN_MIGRATE int BBsetstate_BM: (band members)========================if badguys near if in shelter setaction to leave shelter ret BB_DOACTION else if doing action, stop action if has orders, ret BBsetstate_orders else ret BB_ATTACKelse if has orders, ret BBsetstate_orderselse if doing action, ret BB_DOACTIONelse ret BB_STAND int BBsetstate_flunkey (pet land animals, warriors, and companions)=========================if far from owner, ret BB_MOVETO_OWNERelse if badguys near if has orders, ret BBsetstate_orders else ret BB_ATTACKelse if has orders, ret BBsetstate_orderselse if talking to BM, ret BB_STANDelse if >20 from owner, ret BB_MOVETO_OWNERelse ret BB_STAND int BBsetstate_avian_flunkey (pet avians)=========================if far from owner, ret BB_AVIAN_MOVETO_OWNERelse if badguys near if has orders, ret BBsetstate_avian_orders else ret BB_AVIAN_ATTACKelse if has orders, ret BBsetstate_avian_orderselse if talking to BM, ret BB_AVIAN_STANDelse if >20 from owner, ret BB_AVIAN_MOVETO_OWNERelse ret BB_AVIAN_STAND int BBsetstate_friendly (friendly cavemen)========================= if CRH caveman within 500 of home if >50 from home, ret BB_MOVETO_HOME else if badguys near home, ret BB_ATTACK else if talking to BM, ret BB_STAND else ret BBsetstate_grazeelse if badguys near, ret BB_ATTACK else if talking to BM, ret BB_STAND else ret BBsetstate_graze int BBsetstate_defloc (cavemen or land animals defending a location)==================if far from loc, ret BB_MOVETO_LOCelse if badguys near, ret BB_ATTACKelse ret BB_STAND int BBsetstate_hostile (hostile cavemen)============================if badguys near, ret BB_ATTACKelse ret BBsetstate_graze int BBsetstate_thief (thieves)===========================if in approach mode, ret_BB_THIEF_APPROACHelse ret BB_THIEF_RUN int BBsetstate_predator (wild land predators)=======================================================if hunting if carcass nearby, ret_BB_EATKILL else if has tgt, ret BB_HUNT else ret BB_FLOCKelse if eating if carcass nearby, ret BB_EATKILL else ret BB_FLOCKelse ret BB_FLOCK int BBsetstate_avian_predator (wild avian predators)=======================================================if hunting if carcass nearby, ret_BB_AVIAN_EATKILL else if has tgt, ret BB_AVIAN_HUNT else ret BB_AVIAN_FLOCKelse if eating if carcass nearby, ret BB_AVIAN_EATKILL else ret BB_AVIAN_FLOCKelse ret BB_AVIAN_FLOCK int BBsetstate_defend (wild land animals w/ defend AI)==============================if badguys near, ret BB_ATTACKelse ret BBsetstate_graze int BBsetstate_avian_defend (wild avians w/ defend AI)==============================if badguys near, ret BB_AVIAN_ATTACKelse ret BBsetstate_avian_graze int BBsetstate_maintain_distance (wild land animals)=========================if badguys near, ret BB_FLEEelse ret BBsetstate_graze int BBsetstate_avian_maintain_distance (wild avians)=========================if badguys near, ret BB_AVIAN_FLEEelse ret BBsetstate_avian_graze int BBsetstate (13 types of AI)==================================if avian result=BBsetstate_commonelse result=BBsetstate_avain_commomif result != -1 ret resultif BM, BBsetstate_BMelse if avian flunkey, BBsetstate_avian_flunkeyelse if flunkey, BBsetstate_flunkeyelse if friendly, BBsetstate_friendlyelse if defloc, BBsetstate_deflocelse if hostile, BBsetstate_hostileelse if thief, BBsetstate_thiefelse if avian_predator, BBsetstate_avian_predatorelse if predator, BBsetstate_predatorelse if avian_defend, BBsetstate_avian_defendelse if defend, BBsetstate_defendelse if avian_maintain_distance, BBsetstate_avian_maintain_distanceelse BBsetstate_maintain_distance BBrunstate (24 states)=============================BB_DOACTION do action modeBB_ATTACK, BB_HUNT: turn2tgt(closest badguy) if facing(closest badguy) run do attackBB_AVIAN_ATTACK, BB_AVIAN_HUNT: if tgt behind and rng<50 climb run else turn2tgt(closest badguy) set pitch run do attackBB_COLLSION_RECOVERY turn2dir(CR direction) if facing(CR direction) run inc CR counterBB_AVIAN_COLLSION_RECOVERY turn2dir(CR direction) climb run inc CR counterBB_FLEE turnfromtgt(closest badguy) runBB_AVIAN_FLEE turnfromtgt(closest badguy) climb runBB_FLOCK turn2tgt(leader) walkBB_AVIAN_FLOCK turn2tgt(leader) set pitch runBB_STAND recover fatigueBB_AVIAN_STAND if airborne, land else recover fatigueBB_MOVETO_OWNER turn2tgt(owner) runBB_AVIAN_MOVETO_OWNER turn2tgt(owner) set pitch runBB_WANDER turn2dir(wander dir) walkBB_AVIAN_WANDER turn2dir(wander dir) set pitch runBB_MIGRATE turn2dir(migrate dir) walkBB_AVIAN_MIGRATE turn2dir(migrate dir) set pitch runBB_MOVETO_LOC turn2loc(loc) if avian, set pitch runBB_EATKILL turn2tgt(closest carcass) if at carcass, eat else walkBB_AVIAN_EATKILL turn2tgt(closest carcass) set pitch if at carcass if airborne, land else eat else runBB_THIEF_APPROACH turn2tgt(player) run if at tgt, do dialogBB_THIEF_RUN turnfromtgt(player) run
.
.
.
Then it occured to me that the setstate/runstate pattern is sort of yet another example of unnecessary deferred processing. if i'm in setstate, and determine a desired state, right then i know which code in the case statement in runstate to run, so why not put the code in a routine and just call it right then and there? so the basic architecture of the AI evolved again. The follow is an example of the basic design iterations:
.ORIGINAL WAY:for each unit set tgt (based on unit type) steer (based on situation and AI type) move (based on situation and AI type) do attack (if badguy in strikezone, start attack)SETSTATE/RUNSTATE WAY:void setstate{if (condition A) state=Aelse if (condition B) state=B...}void runstate{switch(state)case A: // code to handle state A breakcase B: // code to handle state B break...}NEW WAY:void run_AI{if (condition A) dostateA()else if (condition B) dostateB()...}void dostateA(){// code to handle state A }void dostateB(){// code to handle state B }
.
.
.
and here's the new AI, ready to cut and paste into the code and translate from pseudocode to real code. this version i named the "B2" api, so
its functions and pre-defined constants begin with "B2". As i wrote it, i split it up based on call hierarchy, just to see how many levels of AI there were. Turns out there are seven levels of AI, with run_AI at the highest level, and calc_amount2turn, turn_unit, and calc_dir2loc at the lowest level (move_unit ended up at the second lowest level).
.------------------------------------------------------------LEVEL 1 (top level) int B2run_AI (13 types of AI, 24 runstates)==================================if avian if do_avian_common_AI, retelse if do_common_AI, retif BM, B2run_BMelse if avian flunkey, B2run_avian_flunkeyelse if flunkey, B2run_flunkeyelse if friendly, B2run_friendlyelse if defloc, B2run_deflocelse if hostile, B2run_hostileelse if thief, B2run_thiefelse if avian_predator, B2run_avian_predatorelse if predator, B2run_predatorelse if avian_defend, B2run_avian_defendelse if defend, B2run_defendelse if avian_maintain_distance, B2run_avian_maintain_distanceelse B2run_maintain_distance -------------------------------------------------------------------------------------LEVEL 2 int B2do_common_AI (returns 1 if called a runstate routine)=========================result=1if cornered, B2attackelse if in collision recovery, B2collision_recoveryelse if halfdead, B2fleeelse if badguy within 20 if badguy stronger, B2flee else B2attackelse if taking fire if have tgt, B2attack else B2flockelse if resting B2standelse result=0ret result int B2do_avian_common_AI (returns 1 if called a runstate routine)=========================result=1if cornered, B2avian_attackelse if in collision recovery, B2avian_collision_recoveryelse if halfdead, B2avian_fleeelse if badguy within 20 if badguy stronger, B2avian_flee else B2avian_attackelse if taking fire if have tgt, B2avain_attack else B2avian_floackelse if resting B2avian_standelse result=0ret result int B2run_BM: (band members)========================if badguys near if in shelter setaction to leave shelter B2doaction else if doing action, stop action if has orders, B2do_orders else B2attackelse if has orders, B2do_orderselse if doing action, B2doactionelse B2stand int B2run_flunkey (pet land animals, warriors, and companions)=========================if far from owner, B2moveto_ownerelse if badguys near if has orders, B2do_orders else B2attackelse if has orders, B2do_orderselse if talking to BM, B2standelse if >20 from owner, B2moveto_ownerelse B2stand int B2run_avian_flunkey (pet avians)=========================if far from owner, B2avian_moveto_ownerelse if badguys near if has orders, B2do_avian_orders else B2avian_attackelse if has orders, B2do_avian_orderselse if talking to BM, B2avian_standelse if >20 from owner, B2avian_moveto_ownerelse B2avian_stand int B2run_friendly (friendly cavemen)========================= if CRH caveman within 500 of home if >50 from home, B2moveto_home else if badguys near home, B2attack else if talking to BM, B2stand else B2grazeelse if badguys near, B2attack else if talking to BM, B2stand else B2graze int B2run_defloc (cavemen or land animals defending a location)==========================================if far from loc, B2moveto_locelse if badguys near, B2attackelse B2stand int B2run_hostile (hostile cavemen)============================if badguys near, B2attackelse B2graze int B2run_thief (thieves)===========================if in approach mode, B2thief_approachelse B2thief_run int B2run_predator (wild land predators)=======================================================if hunting if carcass nearby, B2eatkill else if has tgt, B2hunt else B2flockelse if eating if carcass nearby, B2eatkill else B2flockelse B2flock int B2run_avian_predator (wild avian predators)=======================================================if hunting if carcass nearby, B2avian_eatkill else if has tgt, B2avian_hunt else B2avian_flockelse if eating if carcass nearby, B2avian_eatkill else B2avian_flockelse B2avian_flock int B2run_defend (wild land animals w/ defend AI)==============================if badguys near, B2attackelse B2graze int B2run_avian_defend (wild avians w/ defend AI)==============================if badguys near, B2avian_attackelse B2avian_graze int B2run_maintain_distance (wild land animals)=========================if badguys near, B2fleeelse B2graze int B2run_avian_maintain_distance (wild avians)=========================if badguys near, B2avian_fleeelse B2avian_graze --------------------------------------------------------------------LEVEL 3 int B2do_orders=============================set state based on the unit's orders int B2do_avian_orders=============================set state based on the avian unit's orders int B2graze=======================as current graze, adds migrate.calls: B2stand, B2wander, B2flock, B2migrate int B2avian_graze=======================as current graze, adds migrate.calls: B2avian_stand, B2avian_wander, B2avian_flock, B2avian_migrate ------------------------------------------------------------------------------LEVEL 4low level routines, perform the desired actions: B2doaction=============call doaction_mode B2attack=============B2turn2tgt(closest badguy)if B2is_facing_tgt(closest badguy) run do attack B2hunt============call B2attack B2avian_attack===============if tgt behind and rng<50 climb runelse turn2tgt(closest badguy) set pitch run do attack B2avian_hunt====================call B2avian_attack B2collision_recovery==========================turn2dir(CR direction)if is_facing(CR direction) run inc CR counter B2avian_collision_recovery=============================turn2dir(Collision Recovery direction)climbruninc Collision Recovery counter B2flee===========================turnfromtgt(closest badguy)run B2avian_flee========================turnfromtgt(closest badguy)climbrun B2flock======================turn2tgt(leader)walk B2avian_flock====================turn2tgt(leader)set pitchrun B2stand=================recover fatigue B2avian_stand========================if airborne, landelse recover fatigue B2moveto_owner========================turn2tgt(owner)run B2avian_moveto_owner================================turn2tgt(owner)set pitchrun B2wander========================turn2dir(wander dir)walk B2avian_wander===========================turn2dir(wander dir)set pitchrun B2migrate========================turn2dir(migrate dir)walk B2avian_migrate====================turn2dir(migrate dir)set pitchrun B2moveto_loc=======================turn2loc(loc)run B2eatkill========================turn2tgt(closest carcass)if at carcass, eatelse walk B2avian_eatkill=========================turn2tgt(closest carcass)set pitchif at carcass if airborne, land else eatelse run B2thief_approach=====================turn2tgt(player)runif at tgt, do dialog B2thief_run=======================turnfromtgt(player)run ------------------------------------------------------------------------------------------LEVEL 5 B2turn2tgt(closest badguy, leader, NPC owner, closest carcass, BM owner, or player) ======================================================================calc dir to tgtcall B2turn2dir(dir to tgt) B2turn_from_tgt(closest badguy,player)======================================calc dir to tgtcall B2turn2dir(dir to tgt + 180) B2turn2loc(loc)====================================dir=B2calc_dir2locB2turn2dir(dir) B2is_facing_tgt(closest badguy) returns 1 if unit is facing its tgt=====================================================dir=B2calc_dir2tgtif B2is_facing_dir(dir) ret 1ret 0 B2tgt_is_behind returns 1 if tgt is behind the unit=========================================================dir=B2calc_dir2tgtif B2is_facing_dir(dir+180) ret 1ret 0 if airborne: =================================if unit y > heightmap at unit x,z, ret 1ret 0 if at carcass=====================a=closest_carcassif B2rng2tgt(a)<10 ret 1ret 0 B2climb========================set pitch up B2set_pitch===========================set pitch as required by situation B2run=====================move at sprint/run speedcalc amount to moveB2move_unit(amount) B2walk=====================move at walk speedcalc amount to moveB2move_unit(amount) B2land=================================diverunif alt <=0, alt=0 B2eat===========================if full, hunting=false B2do thief dialog==============================show dialogif get stuff, thief approach mode=0else make thief a hostle B2recover_fatigue (standing)=============================decerase fatiguellim fatigue to 0 B2do_attack================call do_animal_attack B2do_avian_attack=======================call do_avian_attack B2incCRcounter===================counter++if counter >= MAX, in_collision_recovery=false -------------------------------------------------------------------------------------LEVEL 6 B2turn2dir(CR dir,wander dir,migrate dir)====================================================amount=B2calc_amount2turnB2turn_unit(amount) B2is_facing_dir(CR dir) returns 1 if the unit is facing in the indicated direction==============================================================if abs ( dir - unit yr ) <= 30 ret 1ret 0 B2rng2tgt (<50, at tgt) =============================================returns rng to tgt B2calc_dir2tgt===========================loc=tgt loccall B2calc_dir2loc get_closest_badguy=========================definition of "badguy" based on unit type.does best search of band members and/or animals get_owner ============================animal[n].owner, and animal[n].owner_is_caveman get_leader===================================== findfirst, same species (same animal type) get_closest_carcass======================================best search of animals: active, dead, not butchered. get_player_controlled_BM==============================return cm0 calc_amount2move========================================================// speed is based on walk/run/sprintif using missile wpn, desired rng=missile wpn rngelse desired rng=unit rad+tgt radamount=rng2tgt-desired rngif amount > speed, amount = speedif amount < -speed, amount = -speed B2move_unit(amount)======================================new loc=current loc+move amountif no collisions, current loc = new loc ----------------------------------------------------------------------------------------------------LEVEL 7 B2calc_amount2turn=====================================given desired dir, current dir, max turnrate:amount=desired-currentlimit( &amount, -max, max) B2turn_unit(amount)========================================animal[n].yr+=amountwhile (animal[n].yr >= 2*pi) animal[n].yr-=2*piwhile (animal[n].yr < 0) animal[n].yr+=2*pi B2calc_dir2loc(loc)================================atan dz/dx or something like that
.
.
.
once this is implemented, audio and action animations are the only really big things left to do.
Norman Barrows
How can i kill thee? let me count the ways...

There are lots of ways to die in Caveman. For a long time, I've been wanting to post a list, but have never gotten around to it until now. I've been play testing the past few days. Now I'm implementing the changes that play testing uncovered. Then I'll post beta 15. One change is taking 1 dp as well as getting sick from eating raw or spoiled food. But if you take damage, you can die! So now there's yet another way to die in Caveman! . This got me to wondering just how many ways you could die now in Caveman, so I searched the code, and made a list:

Ways to die in Caveman:
--------------------------------------------
1. killed by hostile caveman
2. killed by wild animal
3. swept away by flood waters and drown
4. food poisoning - NEW! before was only indirectly through death by illness (caused by food poisoning).
5. drug overdose
6. dehydration
7. illness
8. exposure to cold
9. heatstroke
10. starvation
11. volcano
12. earthquake
13. comet strike
14. meteor shower
15. hailstorm
16. wildfire
17. fall from tree while climbing up or down
18. falling to your death (fall off cliff, etc)
19. background radiation (old age)
20. drown while swimming

did I forget any? .
Norman Barrows
Caveman 3.0 progress report April 20th 2014

Work on Caveman continues. Its down to high level features and polishing. Currently, the quest generator is under construction, with 14 types of quests implemented so far.

Also the Beta has begun.

Check it out and let me know what you think!

https://sites.google.com/site/rocklandsoftware/home/cmbeta1

Please send feedback to rocklandsoftware at gmail dot com. Thanks!

If you find its your kind of game, I'm still looking for more play testers.



Recently done:

* added chance to discover you're lost while travelling cross-country, and stop.
* "view model" assigned to F4 by default
* "save game" assigned to F5 by default
* move slower uphill and faster downhill. speed based on slope.
* cut number of rocks in rocks terrain in half
* adjustable diff levels for:
> hit points
> caveman encounter chance
> animal encounter chance
the game already has adjustable healing rate.
* pushes action (saves it) when interrupted by sleep, resumes action when you wake up.
* number keys for select wpn
* added ability to yield while in combat.
* NPCs switch to "stand around" AI when combat ends.
* added trader encounters
* added ability to strip gear from dead bodies.
* more realistic gear loadout for npc's, including small chance for artifact.
* added thief encounters.
* fixed: hut NPC encounters were not setting NPC's house_index for new npcs.
* fixed: cliprng not set for hut doors and centerpole.
* demo version
* beta version
* huts and caves are no longer created in canyons
* cross country movement is now at walk speed.
* system requirements are now displayed when unable to create d3d device due to
pc not meeting minimum system requirements of 1600x900, 32bit ARGB, 24 bit depth buffer.
* improved frustum culling. terrain chunks behind player are
no longer culled in 3pv max zoom out looking straight down.
* fixed: swamp ground texture wrong.
* fixed: skills display, throwing rock atk text too wide
* fixed: skills display, throwing axe atk text too wide
* dead npc: the game now changes wih (weapon in hand) to hands if the player takes the npc's wih.
* more trees in northern savanna
* clear all terrain chunks if player doesn't take over abandoned hut (makes hut disappear)
* removed "bury" from dead caveman menu
* fixed: stream quads don't quite reach to bank (about 1" short)
* fixed: check fire - shows fuel left
* fixed: recover from illness too fast?
* fixed: fruit spoils too fast?
* double the rate of fatigue increase when sprinting
* added double quote to caveman font
* fixed: local map scrolls a little too fast
* speedups: indexing huts, indexing caves, updating crh
(cave, rockshelter, hut) stufflists (what they have to trade).
* new all-in-one "start new game" screen
* improved heightmap formulas for flowing water. reduced z fighting
where size of flowing water changes at map square edges.
* fixed: 1pv 1 hand stone hammer male atk ani stone not drawn in hand correctly (off by 2 inches)
* added pound sign to caveman font
* fixed: female vest armor too small
* forrage action: now shows food stat
* ability to block all the time, even when you weapon is not drawn.
* sprinting and fatigue modeling for npc's and animals.
* draw shield in 1pv block ani
* tutorial
* gamespeed 5. 1 screen per hour, does not draw the scene. therefore no need
to generate terrain chunks for each screen if travelling cross country.
* fixed: river action area too samll
* full fatigue no longer triggers rest action. now it reduces player to walk speed.
* added animal and NPC attack fatigue modeling.
* gamespeed 6. one screen per hour, does not draw the scene. does not run animals or
npc's. only available when no badguys are nearby.
* questgen: 14 types of quests so far.

time to implement the next type of quest!
Norman Barrows
[color=rgb(40,40,40)][font=arial]CAVEMAN 3.0 general description.[/font][/color]
[color=rgb(40,40,40)][font=arial]Someone suggested i post a description of CAVEMAN as a reference link in threads about the game.[/font][/color]

[color=rgb(40,40,40)][font=arial]CAVEMAN is a fully immersive 3d Paleo-World simulator. Its a persistent modifiable open world RPG / person sim, with a fps / 3ps interface.[/font][/color]

[color=rgb(40,40,40)][font=arial]so its sort of a cross of a fps like Oblivion, an RPG like table top D&D or Traveller, and a person sim like The SIMs.[/font][/color]

[color=rgb(40,40,40)][font=arial]you can do all the normal rpg stuff, plus you can select almost anything in the environment and interact with it like in The SIMs.[/font][/color]

[color=rgb(40,40,40)][font=arial]The setting is paleolithic, about 200,000 to 10,000 years ago. no techno. no magic.[/font][/color]

[color=rgb(40,40,40)][font=arial]the player can control a band of up to 10 cavemen (like a household in The SIMs).[/font][/color]

[color=rgb(40,40,40)][font=arial]Band members have core stats like strength, intelligence, etc. - the usual RPG stuff.[/font][/color]

[color=rgb(40,40,40)][font=arial]Band members also have variable stats as seen in The SIMs, like food, water, hygiene, and mood.[/font][/color]

[color=rgb(40,40,40)][font=arial]46 skills and experience in each. You can research, teach, and learn skills, as well as acquire then through use.[/font][/color]

[color=rgb(40,40,40)][font=arial]no classes.[/font][/color]

[color=rgb(40,40,40)][font=arial]a 2500x2500 mile randomly generated, persistent, modifiable world. The "ground mesh" for the world is procedurally generated in chunks on the fly as needed from data structures containing the "world map" (right down to the last rock, plant, and clump of grass). The entire world ground mesh is [/font][/color]87.12 TRILLION triangles[color=rgb(40,40,40)][font=arial].[/font][/color]

[color=rgb(40,40,40)][font=arial]Over 200 types of objects you can make or find.[/font][/color]

[color=rgb(40,40,40)][font=arial]62 types of weapons.[/font][/color]

[color=rgb(40,40,40)][font=arial]Over 50 types of extinct Mega-fauna to hunt or be hunted by.[/font][/color]

[color=rgb(40,40,40)][font=arial]thousands of possible actions, from filling water skins, to repairing a bow.[/font][/color]

[color=rgb(40,40,40)][font=arial]exploration, resource gathering, crafting, and trade.[/font][/color]

[color=rgb(40,40,40)][font=arial]In depth modeling and simulation of lots of stuff, from windage effects on arrows to death by background radiation (IE old age).[/font][/color]

[color=rgb(40,40,40)][font=arial]The player starts with one band member, but can get NPCs to join their band.[/font][/color]

[color=rgb(40,40,40)][font=arial]The objective is for your band to become as powerful as possible and last as long as possible.[/font][/color]

[color=rgb(40,40,40)][font=arial]The game ends if all your band members die. [/font][/color]