1909 lines
43 KiB
C
1909 lines
43 KiB
C
#include <assert.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include "ai.h"
|
|
#include "attack.h"
|
|
#include "data.h"
|
|
#include "io.h"
|
|
#include "flag.h"
|
|
#include "god.h"
|
|
#include "lf.h"
|
|
#include "map.h"
|
|
#include "move.h"
|
|
#include "nexus.h"
|
|
#include "objects.h"
|
|
#include "save.h"
|
|
#include "spell.h"
|
|
#include "text.h"
|
|
#include "vault.h"
|
|
|
|
material_t *material = NULL,*lastmaterial = NULL;
|
|
objectclass_t *objectclass = NULL,*lastobjectclass = NULL;
|
|
objecttype_t *objecttype = NULL,*lastobjecttype = NULL;
|
|
brand_t *firstbrand = NULL,*lastbrand = NULL;
|
|
obmod_t *firstobmod = NULL,*lastobmod = NULL;
|
|
celltype_t *firstcelltype = NULL,*lastcelltype = NULL;
|
|
behaviour_t *firstbehaviour = NULL,*lastbehaviour = NULL;
|
|
command_t *firstcommand = NULL,*lastcommand = NULL;
|
|
race_t *firstrace = NULL,*lastrace = NULL;
|
|
raceclass_t *firstraceclass = NULL,*lastraceclass = NULL;
|
|
recipe_t *firstrecipe = NULL,*lastrecipe = NULL;
|
|
job_t *firstjob = NULL,*lastjob = NULL;
|
|
poisontype_t *firstpoisontype = NULL,*lastpoisontype = NULL;
|
|
subjob_t *firstsubjob = NULL,*lastsubjob = NULL;
|
|
skill_t *firstskill = NULL,*lastskill = NULL;
|
|
habitat_t *firsthabitat = NULL,*lasthabitat = NULL;
|
|
map_t *firstmap = NULL,*lastmap = NULL;
|
|
map_t *heaven = NULL;
|
|
region_t *firstregion = NULL,*lastregion = NULL;
|
|
regionoutline_t *firstregionoutline = NULL,*lastregionoutline = NULL;
|
|
branch_t *firstbranch = NULL,*lastbranch = NULL;
|
|
knowledge_t *knowledge = NULL, *lastknowledge = NULL;
|
|
hiddenname_t *firsthiddenname = NULL, *lasthiddenname = NULL;
|
|
npcname_t *npcname;
|
|
int numnpcnames;
|
|
|
|
extern lifeform_t *godlf[];
|
|
extern int ngodlfs;
|
|
|
|
int nextregionthingid = 0;
|
|
|
|
buildingusage_t buildingusage[MAXBUILDINGTYPES];
|
|
int nbuildingusage = 0;
|
|
|
|
warning_t *firstwarning = NULL,*lastwarning = NULL;
|
|
option_t *firstoption = NULL,*lastoption = NULL;
|
|
|
|
int maxmonhitdice = 0; // highest number of hitdice for any monster
|
|
|
|
double presin[360];
|
|
double precos[360];
|
|
|
|
extern vault_t *firstvault;
|
|
|
|
extern flag_t *retflag[];
|
|
extern int nretflags;
|
|
|
|
glyph_t playerglyph,tempglyph;
|
|
|
|
double startticks,lastticks;
|
|
struct timeval starttv, tv,newtv;
|
|
|
|
extern int enteringmap;
|
|
|
|
// maintains unique lifeform ID numbers
|
|
long nextlfid = 0;
|
|
|
|
int SCREENW = DEF_SCREENW;
|
|
int SCREENH = DEF_SCREENH;
|
|
|
|
// object return list
|
|
object_t *retobs[MAXPILEOBS+1];
|
|
int retobscount[MAXPILEOBS+1];
|
|
int nretobs = 0;
|
|
|
|
FILE *logfile;
|
|
|
|
prompt_t prompt;
|
|
|
|
char msghist[MAXHISTORY][BUFLEN];
|
|
int nmsghist = 0;
|
|
|
|
enum ERROR reason; // global for returning errors
|
|
void *rdata; // globel for returning data
|
|
|
|
lifeform_t *player = NULL;
|
|
int gameover;
|
|
|
|
int obdb = B_FALSE;
|
|
|
|
enum GAMEMODE gamemode = GM_FIRST;
|
|
enum WINGAMETYPE wintype = WT_NONE;
|
|
|
|
long curtime = 0; // # current game clock (in seconds)
|
|
long gamedays = 0; // # game days passed since start of game
|
|
long gamesecs = 0; // # game seconds passed since start of game
|
|
long timeleft = 0; // # secs remaining for which we need to process events
|
|
|
|
extern int statdirty;
|
|
|
|
extern int noredraw;
|
|
|
|
int needredraw = B_TRUE;
|
|
int numdraws = 0;
|
|
|
|
// for xp list debugging
|
|
extern race_t **raceposs;
|
|
extern int *xpposs;
|
|
|
|
int main(int argc, char **argv) {
|
|
object_t *o;
|
|
char welcomemsg[BUFLEN];
|
|
int ch;
|
|
FILE *playerfile = NULL;
|
|
int foundsavegame = B_FALSE;
|
|
int x,y,i;
|
|
cell_t *c,*targc;
|
|
vault_t *v;
|
|
enum SKILLLEVEL slev;
|
|
flag_t *retflag[MAXCANDIDATES];
|
|
int nretflags;
|
|
map_t fakemap;
|
|
cell_t fakecell;
|
|
|
|
atexit(cleanup);
|
|
|
|
while ((ch = getopt(argc, argv, "f:")) != -1) {
|
|
switch (ch) {
|
|
case 'f':
|
|
playerfile = fopen(optarg, "rt");
|
|
if (!playerfile) {
|
|
fprintf(stderr, "cannot open player file: %s\n",optarg);
|
|
exit(1);
|
|
}
|
|
break;
|
|
case 'h':
|
|
case '?':
|
|
default:
|
|
usage(argv[0]);
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
// init params
|
|
if (init()) {
|
|
exit(1);
|
|
}
|
|
|
|
// load savegame, if available
|
|
foundsavegame = loadall();
|
|
if (foundsavegame) {
|
|
region_t *r;
|
|
map_t *m;
|
|
lifeform_t *l;
|
|
// fill in gods
|
|
r = findregionbytype(BH_HEAVEN);
|
|
m = findregionmap(r->id, 1);
|
|
ngodlfs = 0;
|
|
for (l = m->lf ; l ; l = l->next) {
|
|
if (getraceclass(l) == RC_GOD) {
|
|
godlf[ngodlfs++] = l;
|
|
}
|
|
}
|
|
}
|
|
|
|
// init graphics
|
|
initgfx();
|
|
|
|
// warn about vault problems.
|
|
for (v = firstvault ; v ; v = v->next) {
|
|
if (!v->valid) {
|
|
msg("warning: invalid vaultfile '%s'",v->filename); more();
|
|
}
|
|
}
|
|
|
|
// init prompt
|
|
for (i = 0; i < MAXCHOICES; i++) {
|
|
choice_t *ch;
|
|
ch = &prompt.choice[i];
|
|
ch->text = NULL;
|
|
ch->desc = NULL;
|
|
ch->longdesc = NULL;
|
|
}
|
|
|
|
|
|
// if we didn't load a game, add the player
|
|
if (!foundsavegame) {
|
|
char *user,pname[BUFLEN],buf[BUFLEN];
|
|
job_t *j = NULL;
|
|
race_t *startrace = NULL;
|
|
char ch;
|
|
//object_t *o;
|
|
cell_t *where;
|
|
int dir;
|
|
flag_t *f;
|
|
region_t *wregion, *dregion,*hregion;
|
|
map_t *dmap,*surfmap;
|
|
|
|
// create the dungeon layout
|
|
initmaplayout();
|
|
|
|
// populate scroll, potion, etc names
|
|
genhiddennames();
|
|
|
|
// read from rc file if required
|
|
if (playerfile) {
|
|
char *p;
|
|
while (!feof(playerfile)) {
|
|
fgets(buf, BUFLEN, playerfile);
|
|
buf[strlen(buf)-1] = '\0';
|
|
if (strstr(buf, "job:") == buf) {
|
|
p = buf + strlen("job:");
|
|
j = findjobbyname(p);
|
|
if (j) break;
|
|
}
|
|
}
|
|
fseek(playerfile, 0, SEEK_SET);
|
|
}
|
|
|
|
|
|
if (!startrace) {
|
|
race_t *r;
|
|
gamemode = GM_CHARGEN;
|
|
// ask for race
|
|
initprompt(&prompt, "Select your race:");
|
|
ch = 'a';
|
|
for (r = firstrace ; r ; r = r->next) {
|
|
if (hasflag(r->flags, F_PLAYABLE)) {
|
|
char *longdesc;
|
|
longdesc = malloc(HUGEBUFLEN * sizeof(char));
|
|
makedesc_race(r->id, longdesc, B_TRUE, B_TRUE );
|
|
addchoice(&prompt, ch++, r->name, NULL, r, longdesc);
|
|
free(longdesc);
|
|
}
|
|
}
|
|
startrace = NULL;
|
|
while (!startrace) {
|
|
getchoicestr(&prompt, B_FALSE, B_TRUE);
|
|
startrace = prompt.result;
|
|
}
|
|
}
|
|
|
|
if (!j) {
|
|
// ask for job
|
|
initprompt(&prompt, "Select your base job:");
|
|
ch = 'a';
|
|
for (j = firstjob ; j ; j = j->next) {
|
|
char *longdesc;
|
|
longdesc = malloc(HUGEBUFLEN * sizeof(char));
|
|
makedesc_job(j, longdesc);
|
|
if (!hasflag(j->flags, F_NOPLAYER)) {
|
|
addchoice(&prompt, ch++, (j->id == J_GOD) ? "Diety (for debugging)" : j->name, NULL, j, longdesc);
|
|
}
|
|
free(longdesc);
|
|
}
|
|
j = NULL;
|
|
while (!j) {
|
|
getchoicestr(&prompt, B_FALSE, B_TRUE);
|
|
j = prompt.result;
|
|
}
|
|
}
|
|
|
|
// add player in fake cell
|
|
createfakes(&fakemap, &fakecell);
|
|
real_addlf(&fakecell, startrace->id, 1, C_PLAYER); // this will assign 'player'
|
|
// give them basic abilities
|
|
addflag(player->flags, F_CANWILL, OT_A_CHECKSTAIRS, NA, NA, NULL);
|
|
addflag(player->flags, F_CANWILL, OT_A_PRAY, NA, NA, NULL);
|
|
addflag(player->flags, F_CANWILL, OT_A_TRAIN, NA, NA, NULL);
|
|
addflag(player->flags, F_CANWILL, OT_A_DEBUG, NA, NA, NULL); /////////
|
|
|
|
// make the initial level
|
|
|
|
// create world map.
|
|
wregion = findregionbytype(BH_WORLDMAP);
|
|
assert(wregion);
|
|
surfmap = addmap();
|
|
createmap(firstmap, 1, wregion, NULL, D_NONE, NULL);
|
|
|
|
// create realm of gods - must do this first, so that
|
|
// gods get created because any temples.
|
|
hregion = findregionbytype(BH_HEAVEN);
|
|
assert(hregion);
|
|
heaven = addmap();
|
|
createmap(heaven, 1, hregion, NULL, D_NONE, NULL);
|
|
|
|
// create main dungeon
|
|
dregion = findregionbytype(BH_MAINDUNGEON);
|
|
assert(dregion);
|
|
dmap = addmap();
|
|
createmap(dmap, 1, dregion, firstmap, D_DOWN, NULL);
|
|
|
|
// add player in the starting position
|
|
|
|
//where = real_getrandomadjcell(where, WE_WALKABLE, B_ALLOWEXPAND, LOF_DONTNEED, NULL);
|
|
where = findobinmap(dmap, OT_PLAYERSTART);
|
|
if (!where) {
|
|
dblog("fatal error: couldn't find player start position!");
|
|
msg("fatal error: couldn't find player start position!");
|
|
more();
|
|
exit(1);
|
|
}
|
|
if (where->lf) killlf(where->lf);
|
|
movelf(player, where);
|
|
// new remove fakes
|
|
killfakes(&fakemap, &fakecell);
|
|
|
|
|
|
// this is the hole which you fell down to get here.
|
|
addobfast(where->obpile, OT_HOLEINROOF);
|
|
// kill any objects which were already there, or which fell down the hole
|
|
killallobsexcept(where->obpile, OT_HOLEINROOF, OT_NONE);
|
|
|
|
/*o = hasob(where->obpile, OT_PLAYERSTART);
|
|
killob(o);*/
|
|
|
|
// place the portal to realm of gods
|
|
c = getrandomcell(surfmap);
|
|
while (!cellwalkable(NULL, c, NULL)) {
|
|
c = getrandomcell(surfmap);
|
|
}
|
|
o = addobfast(c->obpile, OT_PORTAL);
|
|
targc = findobinmap(heaven, OT_PORTAL);
|
|
// link surface portal
|
|
addflag(o->flags, F_MAPLINK, heaven->id, targc->x, targc->y, NULL);
|
|
|
|
// link heaven portal
|
|
o = hasob(targc->obpile, OT_PORTAL);
|
|
addflag(o->flags, F_MAPLINK, surfmap->id, c->x, c->y, NULL);
|
|
|
|
|
|
|
|
|
|
// get player name from environment vars
|
|
user = getenv("USER");
|
|
if (user) {
|
|
char pname[MAXPNAMELEN];
|
|
snprintf(pname, MAXPNAMELEN, "%s", getenv("USER"));
|
|
addflag(player->flags, F_NAME, NA, NA, NA, pname);
|
|
} else {
|
|
addflag(player->flags, F_NAME, NA, NA, NA, "Anonymous");
|
|
}
|
|
|
|
// give the player their job
|
|
givejob(player, j->id);
|
|
//////////////////////
|
|
|
|
// read cheat info from player file
|
|
if (playerfile) {
|
|
if (parseplayerfile(playerfile, player)) {
|
|
// error!
|
|
exit(0);
|
|
}
|
|
fclose(playerfile);
|
|
// TODO: note that we're cheaing
|
|
}
|
|
// player needs hunger
|
|
addflag(player->flags, F_HUNGER, 0, NA, NA, NULL);
|
|
|
|
// kill any other lifeforms around the caster, to make room for pets.
|
|
for (dir = DC_N; dir <= DC_NW; dir++) {
|
|
cell_t *c;
|
|
c = getcellindir(player->cell, dir);
|
|
if (c && c->lf) {
|
|
killlf(c->lf);
|
|
}
|
|
}
|
|
|
|
// pet / npc / follower
|
|
getflags(player->flags, retflag, &nretflags, F_HASPET, F_NONE);
|
|
for (i = 0; i < nretflags; i++) {
|
|
cell_t *c;
|
|
race_t *r;
|
|
lifeform_t *pet;
|
|
enum OBTYPE avoidob = OT_WATERDEEP;
|
|
|
|
f = retflag[i];
|
|
r = findracebyname(f->text);
|
|
assert(r);
|
|
|
|
// create pet, in view of player if possible.
|
|
c = real_getrandomadjcell(player->cell, WE_WALKABLE, B_ALLOWEXPAND, LOF_NEED, &avoidob, NULL, player);
|
|
assert(c);
|
|
pet = addlf(c, r->id, 1);
|
|
// mark us as its master
|
|
petify(pet, player);
|
|
}
|
|
|
|
|
|
// set up automated shortcuts
|
|
autoshortcut(player, OT_A_COOK);
|
|
|
|
getplayernamefull(pname);
|
|
snprintf(welcomemsg, BUFLEN, "Greetings %s, welcome to nexus!", pname);
|
|
// 00:00 - 23:59
|
|
curtime = rnd(0,DAYSECS-1);
|
|
|
|
} else {
|
|
char pname[BUFLEN];
|
|
getplayernamefull(pname);
|
|
snprintf(welcomemsg, BUFLEN, "Welcome back, %s!", pname);
|
|
}
|
|
|
|
// start game - this will cause debug messages to now
|
|
// go to the log file instead of stdout.
|
|
timeleft = 0; // reset game timer
|
|
|
|
enteringmap = B_FALSE; // no time passes for lfs on a different map to the player.
|
|
|
|
// calculate initial light
|
|
calclight(player->cell->map);
|
|
// pre-calc line-of-sight for player
|
|
//precalclos(player);
|
|
|
|
if (!foundsavegame) {
|
|
player->facing = D_ALL;
|
|
}
|
|
|
|
setlosdirty(player);
|
|
|
|
if (!foundsavegame) {
|
|
// changes for anything within los/lof of player's starting pos:
|
|
// - don't want any mosnters starting here
|
|
// - don't want any impassable objects other than doors
|
|
// - don't want any locked doors
|
|
slev = getskill(player, SK_CARTOGRAPHY);
|
|
for (y = 0; y < player->cell->map->h; y++) {
|
|
for (x = 0; x < player->cell->map->w; x++) {
|
|
c = getcellat(player->cell->map, x, y);
|
|
if (c && (haslos(player, c) || haslof(player->cell, c, LOF_WALLSTOP, NULL))) {
|
|
object_t *o,*nexto;
|
|
if (c->lf && !isplayer(c->lf) && !ispetof(c->lf, player)) {
|
|
killlf(c->lf);
|
|
}
|
|
for (o = c->obpile->first ; o ; o = nexto) {
|
|
nexto = o->next;
|
|
if (hasflag(o->flags, F_IMPASSABLE) && !hasflag(o->flags, F_DOOR)) {
|
|
killob(o);
|
|
continue;
|
|
} else {
|
|
killflagsofid(o->flags, F_LOCKED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
needredraw = B_TRUE;
|
|
statdirty = B_TRUE;
|
|
|
|
// start game
|
|
gamemode = GM_GAMESTARTED;
|
|
|
|
// redo light and player los
|
|
calclight(player->cell->map);
|
|
|
|
// make player face the direction which gives them the most visibility
|
|
// as we check, set all cells around us to start off known.
|
|
if (!foundsavegame) {
|
|
int bestdir = D_NONE;
|
|
int bestlos = -1;
|
|
for (i = DC_N; i <= DC_NW; i++) {
|
|
setfacing(player, i);
|
|
precalclos(player);
|
|
updateknowncells();
|
|
if (player->nlos > bestlos) {
|
|
bestlos = player->nlos;
|
|
bestdir = i;
|
|
}
|
|
}
|
|
player->facing = bestdir;
|
|
|
|
precalclos(player);
|
|
}
|
|
|
|
// show level
|
|
drawscreen();
|
|
|
|
msg("%s",welcomemsg);
|
|
more();
|
|
|
|
// MAIN LOOP
|
|
|
|
// basic flow is:
|
|
//
|
|
// donextturn() - process a turn for a lifeform
|
|
// startlfturn() Rest effects, Damage from floor objects, etc
|
|
// lifeform takes action
|
|
// checkdeath() - check for object/player death. remove dead things.
|
|
// timeeffectsworld() Fires burn out, ice melts, etc
|
|
// Also keeps lf->timespent values under control.
|
|
//
|
|
//
|
|
// redraw screen
|
|
//
|
|
// checkendgame() - has the game ended yet?
|
|
|
|
|
|
gameover = B_FALSE;
|
|
while (!gameover) {
|
|
map_t *curmap;
|
|
curmap = player->cell->map;
|
|
|
|
// default to no redraw - donextturn() will change this if needed.
|
|
needredraw = B_FALSE;
|
|
numdraws = 0;
|
|
|
|
// someone has a turn - this will then call taketime
|
|
donextturn(curmap);
|
|
|
|
// show level (if required)
|
|
drawscreen();
|
|
|
|
//dblog("**** END of turn, numdraws = %d", numdraws);
|
|
|
|
// check end of game
|
|
checkendgame();
|
|
}
|
|
|
|
// identify all objects
|
|
for (o = player->pack->first ; o ; o = o->next) {
|
|
identify(o);
|
|
}
|
|
// show possessions
|
|
dofinaloblist(player->pack);
|
|
// print tombstone
|
|
tombstone(player);
|
|
|
|
//WriteMemLeak();
|
|
return B_FALSE;
|
|
}
|
|
|
|
celltype_t *addcelltype(int id, char *name, int glyph, int colour, int solid, int transparent, enum MATERIAL mat, int floorheight, int hp) {
|
|
celltype_t *a;
|
|
|
|
// add to the end of the list
|
|
if (firstcelltype == NULL) {
|
|
firstcelltype = malloc(sizeof(celltype_t));
|
|
a = firstcelltype;
|
|
a->prev = NULL;
|
|
} else {
|
|
// go to end of list
|
|
a = lastcelltype;
|
|
a->next = malloc(sizeof(celltype_t));
|
|
a->next->prev = a;
|
|
a = a->next;
|
|
}
|
|
lastcelltype = a;
|
|
a->next = NULL;
|
|
|
|
// set props
|
|
a->id = id;
|
|
a->name = strdup(name);
|
|
a->glyph.ch = glyph;
|
|
a->glyph.colour = colour;
|
|
a->solid = solid;
|
|
a->transparent = transparent;
|
|
a->material = findmaterial(mat);
|
|
a->floorheight = floorheight;
|
|
a->hp = hp;
|
|
|
|
a->flags = addflagpile(NULL, NULL);
|
|
|
|
return a;
|
|
}
|
|
|
|
warning_t *addwarning(char *text, int lifetime) {
|
|
warning_t *a;
|
|
|
|
// add to the end of the list
|
|
if (firstwarning == NULL) {
|
|
firstwarning = malloc(sizeof(celltype_t));
|
|
a = firstwarning;
|
|
a->prev = NULL;
|
|
} else {
|
|
// go to end of list
|
|
a = lastwarning;
|
|
a->next = malloc(sizeof(warning_t));
|
|
a->next->prev = a;
|
|
a = a->next;
|
|
}
|
|
lastwarning = a;
|
|
a->next = NULL;
|
|
|
|
// set props
|
|
a->text = strdup(text);
|
|
a->lifetime = lifetime;
|
|
|
|
return a;
|
|
}
|
|
|
|
|
|
void checkdeath(void) {
|
|
lifeform_t *lf, *nextlf;
|
|
int x,y;
|
|
for (lf = player->cell->map->lf; lf ; lf = nextlf) {
|
|
nextlf = lf->next;
|
|
|
|
// check for object death
|
|
removedeadobs(lf->pack);
|
|
// check for death
|
|
if (lf->hp <= 0) {
|
|
// die!
|
|
die(lf);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// check for object death on map
|
|
for (y = 0; y < player->cell->map->h; y++) {
|
|
for (x = 0; x < player->cell->map->w; x++) {
|
|
cell_t *c;
|
|
c = getcellat(player->cell->map, x, y);
|
|
if (c) {
|
|
removedeadobs(c->obpile);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void checkendgame(void) {
|
|
flag_t *f;
|
|
if (!player->alive) {
|
|
gamemode = GM_GAMEOVER;
|
|
gameover = B_TRUE;
|
|
} else if ((f = lfhasflag(player, F_WINNER)) != NULL) {
|
|
gamemode = GM_GAMEOVER;
|
|
gameover = B_TRUE;
|
|
wintype = f->val[0];
|
|
}
|
|
}
|
|
|
|
void cleanup(void) {
|
|
//int i;
|
|
gamemode = GM_CLEANUP;
|
|
|
|
free(xpposs);
|
|
free(raceposs);
|
|
|
|
fclose(logfile);
|
|
cleanupgfx();
|
|
|
|
// free commands & options
|
|
while (firstcommand) killcommand(firstcommand);
|
|
while (firstoption) killoption(firstoption);
|
|
// free maps (this will kill its lifeforms & obs & cells too)
|
|
while (firstmap) killmap(firstmap);
|
|
// free knowledge
|
|
while (knowledge) killknowledge(knowledge);
|
|
// free brands
|
|
while (firstbrand) killbrand(firstbrand);
|
|
// free obmods
|
|
while (firstobmod) killobmod(firstobmod);
|
|
// free obtypes
|
|
while (objecttype) killot(objecttype);
|
|
// free obclasses
|
|
while (objectclass) killoc(objectclass);
|
|
// free materials
|
|
while (material) killmaterial(material);
|
|
// free races
|
|
while (firstrace) killrace(firstrace);
|
|
// free posiontypes
|
|
while (firstpoisontype) killpoisontype(firstpoisontype);
|
|
// free celltypes
|
|
while (firstcelltype) killcelltype(firstcelltype);
|
|
/*
|
|
for (i = 0; i < numnpcnames; i++) {
|
|
// free npcnames
|
|
free(npcname[i].name);
|
|
free(&npcname[i]);
|
|
}
|
|
*/
|
|
free(npcname);
|
|
// free hidden names
|
|
while (firsthiddenname) killhiddenname(firsthiddenname);
|
|
// behaviours
|
|
while (firstbehaviour) killbehaviour(firstbehaviour);
|
|
//WriteMemLeak();
|
|
}
|
|
|
|
void dbtimestart(char *text) {
|
|
gettimeofday(&tv, NULL);
|
|
starttv = tv;
|
|
dblog("START\t%s", text);
|
|
}
|
|
|
|
void dbtime(char *text) {
|
|
double ticks;
|
|
gettimeofday(&newtv, NULL);
|
|
ticks = ((newtv.tv_sec - tv.tv_sec) * 1000000) + (newtv.tv_usec - tv.tv_usec);
|
|
dblog("+%f\t%s", ticks, text);
|
|
tv = newtv;
|
|
}
|
|
|
|
void dbtimeend(char *text) {
|
|
double ticks;
|
|
gettimeofday(&newtv, NULL);
|
|
ticks = ((newtv.tv_sec - starttv.tv_sec) * 1000000) + (newtv.tv_usec - starttv.tv_usec);
|
|
dblog("FINISHED %s (total time %f)", text, ticks);
|
|
}
|
|
|
|
void dobresnham(int d, int xinc1, int yinc1, int dinc1, int xinc2, int yinc2, int dinc2, int *xinc, int *yinc, int *dinc) {
|
|
if (d < 0) {
|
|
*xinc = xinc1;
|
|
*yinc = yinc1;
|
|
*dinc = dinc1;
|
|
} else {
|
|
*xinc = xinc2;
|
|
*yinc = yinc2;
|
|
*dinc = dinc2;
|
|
}
|
|
}
|
|
|
|
|
|
void donextturn(map_t *map) {
|
|
lifeform_t *who;
|
|
int db = B_FALSE;
|
|
map_t *oldpmap;
|
|
|
|
oldpmap = player->cell->map;
|
|
|
|
who = map->lf;
|
|
|
|
if (who) {
|
|
if (db) dblog("**** donextturn for: id %d %s", who->id, who->race->name);
|
|
|
|
|
|
assert(who->timespent == 0);
|
|
|
|
startlfturn(who);
|
|
|
|
// calculate light
|
|
calclight(map);
|
|
// pre-calculate line of sight for this lifeform
|
|
//precalclos(who);
|
|
// cope with eyesight adjusting to the dark
|
|
do_eyesight_adjust(who);
|
|
|
|
// update gun targets
|
|
autotarget(who);
|
|
|
|
// keep looping until they actually do something or are dead!
|
|
while (who->timespent == 0) {
|
|
if (isdead(who)) {
|
|
// skip turn
|
|
taketime(who, SPEED_DEAD);
|
|
} else {
|
|
|
|
// do we need to run away from something?
|
|
if (!flee(who)) {
|
|
int donormalmove = B_TRUE;
|
|
flag_t *f;
|
|
|
|
// charmed ?
|
|
if (donormalmove) {
|
|
f = lfhasflag(who, F_CHARMEDBY);
|
|
if (f) {
|
|
if (!charmedaction(who, f)) {
|
|
donormalmove = B_FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// casting a spell?
|
|
if (donormalmove) {
|
|
f = lfhasflag(who, F_CASTINGSPELL);
|
|
if (f) {
|
|
donormalmove = B_FALSE;
|
|
|
|
f->val[2]--;
|
|
if (f->val[2] <= 0) {
|
|
// spell triggers!
|
|
char *p;
|
|
char buf[BUFLEN];
|
|
int lfid,mapid,x,y,power;
|
|
long obid;
|
|
lifeform_t *targlf;
|
|
object_t *targob;
|
|
cell_t *targcell = NULL;
|
|
map_t *targmap;
|
|
enum OBTYPE sid;
|
|
|
|
sid = f->val[0];
|
|
power = f->val[1];
|
|
|
|
// finished!
|
|
p = f->text;
|
|
p = readuntil(buf, p, ';');
|
|
lfid = atoi(buf);
|
|
p = readuntil(buf, p, ';');
|
|
obid = atol(buf);
|
|
p = readuntil(buf, p, ';');
|
|
mapid = atoi(buf);
|
|
p = readuntil(buf, p, ';');
|
|
x = atoi(buf);
|
|
p = readuntil(buf, p, ';');
|
|
y = atoi(buf);
|
|
|
|
if (lfid >= 0) {
|
|
targlf = findlf(NULL, lfid);
|
|
} else {
|
|
targlf = NULL;
|
|
}
|
|
if (obid >= 0) {
|
|
targob = findobidinmap(who->cell->map, obid);
|
|
} else {
|
|
targob = NULL;
|
|
}
|
|
if (mapid >= 0) {
|
|
targmap = findmap(mapid);
|
|
targcell = getcellat(targmap, x, y);
|
|
}
|
|
taketime(who, getspellspeed(who));
|
|
dospelleffects(who, sid, power, targlf, targob, targcell, B_UNCURSED, NULL, B_FALSE, NULL);
|
|
killflagsofid(who->flags, F_CASTINGSPELL);
|
|
} else {
|
|
if (isplayer(who)) {
|
|
objecttype_t *sp;
|
|
sp = findot(f->val[0]);
|
|
msg("You continue casting %s.", sp->name);
|
|
} else if (cansee(player, who)) {
|
|
flag_t *f2;
|
|
char lfname[BUFLEN];
|
|
// still going...
|
|
getlfname(who, lfname);
|
|
f2 = lfhasflagval(who,F_SPELLCASTCONTTEXT, f->val[0], NA, NA, NULL);
|
|
if (!f2) {
|
|
f2 = lfhasflagval(who,F_SPELLCASTCONTTEXT, OT_NONE, NA, NA, NULL);
|
|
}
|
|
if (f2 && strlen(f2->text)) {
|
|
msg("%s %s.", lfname, f2->text);
|
|
} else {
|
|
msg("%s continues casting a spell.", lfname);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// digging?
|
|
if (donormalmove) {
|
|
f = lfhasflag(who, F_DIGGING);
|
|
if (f) {
|
|
if (isplayer(who) && checkforkey()) {
|
|
msg("Stopped digging.");
|
|
killflag(f);
|
|
} else {
|
|
if (!continuedigging(who)) donormalmove = B_FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// repairing?
|
|
if (donormalmove) {
|
|
f = lfhasflag(who, F_REPAIRING);
|
|
if (f) {
|
|
// this flag might appear more than once. that's okay, we'll handle a new
|
|
// one each time.
|
|
if (isplayer(who) && checkforkey()) {
|
|
msg("Stopped repairing items.");
|
|
killflag(f);
|
|
} else {
|
|
if (!continuerepairing(who, f)) donormalmove = B_FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// eating?
|
|
if (donormalmove) {
|
|
f = lfhasflag(who, F_EATING);
|
|
if (f) {
|
|
object_t *o;
|
|
o = findobbyid(who->pack, atol(f->text));
|
|
if (!o) {
|
|
o = findobidinmap(who->cell->map, atol(f->text));
|
|
}
|
|
if (o && caneat(who, o) && (getoblocation(o) == who->cell)) {
|
|
int cancelled = B_FALSE;
|
|
|
|
// has it gone bad while we're eating it?
|
|
if (isbadfood(o) && isplayer(who) && (getskill(who, SK_COOKING) >= PR_BEGINNER)) {
|
|
char ques[BUFLEN],obname[BUFLEN];
|
|
char ch;
|
|
getobname(o, obname, 1);
|
|
sprintf(ques, "%s has gone bad - stop eating?", obname);
|
|
ch = askchar(ques, "yn", "y", B_TRUE, B_FALSE);
|
|
if (ch == 'y') {
|
|
killflag(f);
|
|
cancelled = B_TRUE;
|
|
}
|
|
}
|
|
|
|
if (!cancelled) {
|
|
if (eat(who,o)) {
|
|
// failed
|
|
killflag(f);
|
|
} else {
|
|
donormalmove = B_FALSE;
|
|
}
|
|
}
|
|
} else {
|
|
killflag(f);
|
|
}
|
|
}
|
|
}
|
|
|
|
// resting?
|
|
if (donormalmove) {
|
|
f = isresting(who);
|
|
if (!f) {
|
|
f = lfhasflag(who, F_TRAINING);
|
|
}
|
|
if (f) {
|
|
// check for interrupt of resting...
|
|
if (isplayer(who) && checkforkey()) {
|
|
msg("Stopped %s.",(f->id == F_TRAINING) ? "training" : "resting");
|
|
stopresting(who);
|
|
} else {
|
|
if (isplayer(who)) {
|
|
if (++who->turnsskipped >= 10) {
|
|
//msg("Time passes...");
|
|
msg(".");
|
|
who->turnsskipped = 0;
|
|
}
|
|
}
|
|
rest(who, B_TRUE);
|
|
donormalmove = B_FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (donormalmove) {
|
|
// paralyzed etc?
|
|
if (isimmobile(who)) {
|
|
if (isplayer(who)) {
|
|
if (++who->turnsskipped >= 10) {
|
|
//msg("Time passes...");
|
|
msg(".");
|
|
who->turnsskipped = 0;
|
|
}
|
|
}
|
|
rest(who, B_FALSE);
|
|
donormalmove = B_FALSE;
|
|
}
|
|
}
|
|
|
|
if (donormalmove) {
|
|
if (isplayer(who)) {
|
|
drawcursor();
|
|
// find out what player wants to do
|
|
handleinput();
|
|
if (who->bartimer) {
|
|
who->bartimer--;
|
|
if (who->bartimer == 0) {
|
|
who->damlastturn = 0;
|
|
who->mplastturn = 0;
|
|
who->stamlastturn = 0;
|
|
statdirty = B_TRUE;
|
|
drawstatus();
|
|
}
|
|
}
|
|
} else {
|
|
//char lfname[BUFLEN];
|
|
//char buf[BUFLEN];
|
|
// do ai move
|
|
//real_getlfname(who, lfname, B_FALSE);
|
|
//snprintf(buf, BUFLEN, "aimove %s",lfname);
|
|
//dbtimestart(buf);
|
|
aiturn(who);
|
|
//dbtimeend(buf);
|
|
}
|
|
}
|
|
if (!isplayer(who) && (who->timespent == 0) && !donormalmove) {
|
|
// our auto action failed!
|
|
taketime(who, getactspeed(who));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// us or the player moved into a new map? stop turn.
|
|
if ((who->cell->map != map) || (player->cell->map != map)) {
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
if (hasflag(player->flags, F_ASLEEP)) {
|
|
needredraw = B_FALSE;
|
|
}
|
|
|
|
if (!isdead(who)) endlfturn(who);
|
|
} // end 'if (who)'
|
|
|
|
|
|
// check for death etc
|
|
checkdeath();
|
|
|
|
//////////////////////////////////
|
|
// effects which happen every GAME TICK
|
|
// ie. object hp drain etc
|
|
//////////////////////////////////
|
|
|
|
// note: can't use 'who->' below since 'who' might have died
|
|
// and been de-alloced during checkdeath() above if they
|
|
// died.
|
|
//timeeffectsworld(player->cell->map);
|
|
timeeffectsworld(map, B_TRUE);
|
|
|
|
// the previous call to timeeffectsworld might cause the player to
|
|
// change levels (ie. falling down through one or more pits).
|
|
//
|
|
// if this happens, we need to call it again to make sure that ->timespent
|
|
// values don't get out of whack.
|
|
while (player->cell->map != oldpmap) {
|
|
oldpmap = player->cell->map;
|
|
timeeffectsworld(player->cell->map, B_FALSE);
|
|
}
|
|
}
|
|
|
|
command_t *findcommand(enum COMMAND id) {
|
|
command_t *c;
|
|
for (c = firstcommand ; c ; c = c->next) {
|
|
if (c->id == id) return c;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
warning_t *findwarning(char *text) {
|
|
warning_t *w;
|
|
for (w = firstwarning ; w ; w = w->next) {
|
|
if (streq(w->text, text)) return w;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void gettrrange(int depth, int *min, int *max, int range, int oodok) {
|
|
int mid;
|
|
|
|
// adjust depth for out-of-depth monsters
|
|
if (oodok && (onein(6))) {
|
|
depth++;
|
|
// repeated chances of incing level
|
|
while (onein(6)) {
|
|
depth++;
|
|
}
|
|
}
|
|
|
|
//mid = (depth/2);
|
|
mid = (depth);
|
|
|
|
*min = mid - range - 10;
|
|
//limit(min, 0, maxmonhitdice);
|
|
limit(min, 0, 8);
|
|
|
|
*max = mid + range;
|
|
limit(max, *min, maxmonhitdice);
|
|
|
|
|
|
}
|
|
|
|
int getoption(enum OPTION id) {
|
|
option_t *opt;
|
|
for (opt = firstoption ; opt ; opt = opt->next) {
|
|
if (opt->id == id) {
|
|
if (opt->enabled) return B_TRUE;
|
|
}
|
|
}
|
|
return B_FALSE;
|
|
}
|
|
|
|
enum COLOUR getpctcol(float num, float max) {
|
|
float pct;
|
|
pct = (num / max) * 100;
|
|
if (pct > 100) {
|
|
return C_BOLDBLUE;
|
|
} else if (pct == 100) {
|
|
return C_BOLDGREEN;
|
|
} else if (pct >= 75) {
|
|
return C_GREEN;
|
|
} else if (pct >= 50) {
|
|
return C_BROWN;
|
|
} else if (pct >= 25) {
|
|
return C_YELLOW;
|
|
} else { // ie. < 25%
|
|
return C_RED;
|
|
}
|
|
return C_ORANGE;
|
|
}
|
|
|
|
char getpctletter(float num, float max) {
|
|
float pct;
|
|
pct = (num / max) * 100;
|
|
if (pct >= 100) {
|
|
return 'S';
|
|
} else if (pct >= 85) {
|
|
return 'A';
|
|
} else if (pct >= 70) {
|
|
return 'B';
|
|
} else if (pct >= 55) {
|
|
return 'C';
|
|
} else if (pct >= 40) {
|
|
return 'D';
|
|
} else { // ie. < 40%
|
|
return 'E';
|
|
}
|
|
return '?';
|
|
}
|
|
|
|
void getrarityrange(int depth, int *min, int *max, int range, int oodok) {
|
|
int mid;
|
|
int num;
|
|
|
|
// adjust depth for out-of-depth things
|
|
if (oodok && (onein(6))) {
|
|
// repeated 1/3 chances of incing level
|
|
num = rnd(1,6) - 4;
|
|
while (num > 0) {
|
|
depth += num;
|
|
num = rnd(1,6) - 4;
|
|
}
|
|
}
|
|
|
|
mid = 100 - (depth * 3);
|
|
*min = mid - range; if (*min < 0) *min = 0;
|
|
// *max = mid + range; if (*max > 100) *max = 100;
|
|
*max = 100;
|
|
|
|
//if (*min > 85) *min = 85;
|
|
if (*max < 25) *max = 25;
|
|
}
|
|
|
|
int init(void) {
|
|
int i;
|
|
// random numbers
|
|
srand(time(NULL));
|
|
|
|
// precalc sin/cos
|
|
for (i = 0; i < 360; i++) {
|
|
double rads;
|
|
rads = i * (M_PI / 180);
|
|
presin[i] = sin(rads);
|
|
precos[i] = cos(rads);
|
|
}
|
|
|
|
gamemode = GM_INIT;
|
|
|
|
playerglyph.ch = '@';
|
|
playerglyph.colour = C_GREY;
|
|
tempglyph.ch = '@';
|
|
tempglyph.colour = C_GREY;
|
|
|
|
// load npc names
|
|
loadnpcnames();
|
|
|
|
initoptions();
|
|
initcommands();
|
|
initobjects();
|
|
validatehiddennames();
|
|
initraceclasses();
|
|
initskills();
|
|
initjobs();
|
|
initrace();
|
|
initmap();
|
|
|
|
// open log file (want to do this before digging the first map)
|
|
logfile = fopen("log.txt","wt");
|
|
fprintf(logfile, "\n\n\n====== NEW LOGFILE ====\n");
|
|
|
|
validatehiddennames();
|
|
|
|
|
|
gamemode = GM_VALIDATION;
|
|
if (validateobs()) {
|
|
return B_TRUE;
|
|
}
|
|
if (validateraces()) {
|
|
return B_TRUE;
|
|
}
|
|
|
|
|
|
// load in vaults
|
|
loadvaults();
|
|
|
|
// validate regions
|
|
if (validateregions()) {
|
|
printf("Errors found in map outlines - check log for details. Bailing out.");
|
|
exit(1);
|
|
}
|
|
|
|
return B_FALSE;
|
|
}
|
|
|
|
// retcell[0] will be initial location
|
|
void calcbresnham(map_t *m, int x1, int y1, int x2, int y2, cell_t **retcell, int *numpixels) {
|
|
int xinc1,xinc2,yinc1,yinc2,dinc1,dinc2,d;
|
|
int xinc,yinc,dinc;
|
|
int i;
|
|
int x,y;
|
|
initbresnham( x1, y1, x2, y2, &xinc1, &yinc1, &dinc1, &xinc2, &yinc2, &dinc2, numpixels, &d);
|
|
x = x1;
|
|
y = y1;
|
|
for (i = 0; i < *numpixels; i++) {
|
|
retcell[i] = getcellat(m, x, y);
|
|
if (!retcell[i]) { // we've gone off the map - stop here
|
|
*numpixels = i;
|
|
return;
|
|
}
|
|
dobresnham(d, xinc1, yinc1, dinc1, xinc2, yinc2, dinc2, &xinc, &yinc, &dinc);
|
|
// move to next cell
|
|
d += dinc;
|
|
x += xinc;
|
|
y += yinc;
|
|
}
|
|
}
|
|
|
|
void initbresnham(int x1, int y1, int x2, int y2, int *xinc1, int *yinc1, int *dinc1, int *xinc2, int *yinc2, int *dinc2, int *numpixels, int *d) {
|
|
int deltax,deltay;
|
|
|
|
deltax = (x2 - x1);
|
|
if (deltax < 0) deltax = -deltax;
|
|
deltay = (y2 - y1);
|
|
if (deltay < 0) deltay = -deltay;
|
|
|
|
if (deltax >= deltay) {
|
|
*numpixels = deltax + 1;
|
|
*d = (deltay*2) - deltax;
|
|
*dinc1 = deltay << 1;
|
|
*dinc2 = (deltay-deltax) << 1;
|
|
*xinc1 = 1;
|
|
*xinc2 = 1;
|
|
*yinc1 = 0;
|
|
*yinc2 = 1;
|
|
} else {
|
|
*numpixels = deltay + 1;
|
|
*d = (deltax*2) - deltay;
|
|
*dinc1 = deltax << 1;
|
|
*dinc2 = (deltax - deltay) << 1;
|
|
*xinc1 = 0;
|
|
*xinc2 = 1;
|
|
*yinc1 = 1;
|
|
*yinc2 = 1;
|
|
}
|
|
|
|
if (x1 > x2) {
|
|
*xinc1 = - *xinc1;
|
|
*xinc2 = - *xinc2;
|
|
}
|
|
if (y1 > y2) {
|
|
*yinc1 = - *yinc1;
|
|
*yinc2 = - *yinc2;
|
|
}
|
|
}
|
|
|
|
|
|
int isplayerturn(void) {
|
|
if (!player) return B_FALSE;
|
|
|
|
if (isplayer(player->cell->map->lf)) {
|
|
return B_TRUE;
|
|
}
|
|
return B_FALSE;
|
|
}
|
|
|
|
void killwarning(warning_t *w) {
|
|
warning_t *nextone, *lastone;
|
|
|
|
// free mem
|
|
if (w->text) free(w->text);
|
|
|
|
// remove from list
|
|
nextone = w->next;
|
|
if (nextone != NULL) {
|
|
nextone->prev = w->prev;
|
|
} else { /* last */
|
|
lastwarning = w->prev;
|
|
}
|
|
|
|
if (w->prev == NULL) {
|
|
/* first */
|
|
nextone = w->next;
|
|
free(firstwarning);
|
|
firstwarning = nextone;
|
|
} else {
|
|
lastone = w->prev;
|
|
free (lastone->next );
|
|
lastone->next = nextone;
|
|
}
|
|
}
|
|
|
|
void killwarningtext(char *text) {
|
|
warning_t *w;
|
|
w = findwarning(text);
|
|
if (w) killwarning(w);
|
|
}
|
|
|
|
int limit(int *what, int min, int max) {
|
|
int limited = B_FALSE;
|
|
if (min != NA) {
|
|
if (*what < min) {
|
|
*what = min;
|
|
limited = B_TRUE;
|
|
}
|
|
}
|
|
if (max != NA) {
|
|
if (*what > max) {
|
|
*what = max;
|
|
limited = B_TRUE;
|
|
}
|
|
}
|
|
return limited;
|
|
}
|
|
|
|
int limitf(float *what, float min, float max) {
|
|
int limited = B_FALSE;
|
|
if (min != NA) {
|
|
if (*what < min) {
|
|
*what = min;
|
|
limited = B_TRUE;
|
|
}
|
|
}
|
|
if (max != NA) {
|
|
if (*what > max) {
|
|
*what = max;
|
|
limited = B_TRUE;
|
|
}
|
|
}
|
|
return limited;
|
|
}
|
|
|
|
int limitd(double *what, double min, double max) {
|
|
int limited = B_FALSE;
|
|
if (min != NA) {
|
|
if (*what < min) {
|
|
*what = min;
|
|
limited = B_TRUE;
|
|
}
|
|
}
|
|
if (max != NA) {
|
|
if (*what > max) {
|
|
*what = max;
|
|
limited = B_TRUE;
|
|
}
|
|
}
|
|
return limited;
|
|
}
|
|
|
|
int loadnpcnames(void) {
|
|
FILE *f;
|
|
char buf[BUFLEN];
|
|
int i = 0;
|
|
f = fopen("data/npcnames.txt", "rt");
|
|
if (!f) return B_TRUE;
|
|
|
|
// count lines...
|
|
fgets(buf, BUFLEN, f);
|
|
numnpcnames = 0;
|
|
while (!feof(f)) {
|
|
buf[strlen(buf)-1] = '\0'; // strip newline
|
|
if (strlen(buf)) {
|
|
numnpcnames++;
|
|
}
|
|
fgets(buf, BUFLEN, f);
|
|
}
|
|
|
|
// alloc mem
|
|
npcname = malloc(numnpcnames * sizeof(npcname_t));
|
|
|
|
// back to start
|
|
fseek(f, 0, SEEK_SET);
|
|
|
|
// now read in names
|
|
fgets(buf, BUFLEN, f);
|
|
while (!feof(f)) {
|
|
buf[strlen(buf)-1] = '\0'; // strip newline
|
|
if (strlen(buf)) {
|
|
capitalise(buf);
|
|
npcname[i].name = strdup(buf);
|
|
npcname[i].valid = B_TRUE;
|
|
i++;
|
|
}
|
|
fgets(buf, BUFLEN, f);
|
|
}
|
|
return B_FALSE;
|
|
}
|
|
|
|
void *mymalloc(size_t sz) {
|
|
return malloc(sz);
|
|
}
|
|
|
|
int onein(int howmany) {
|
|
if (howmany <= 0) return B_FALSE;
|
|
if (rnd(1,howmany) == 1) return B_TRUE;
|
|
return B_FALSE;
|
|
}
|
|
|
|
int parseplayerfile(FILE *f, lifeform_t *lf) {
|
|
// add extra obs etc from f
|
|
char *pp;
|
|
char localbuf[BUFLEN];
|
|
char buf[BUFLEN];
|
|
int goterror = B_FALSE;
|
|
fgets(buf, BUFLEN, f);
|
|
while (!feof(f)) {
|
|
if (buf[strlen(buf)-1] == '\n') {
|
|
buf[strlen(buf)-1] = '\0';
|
|
}
|
|
//dblog("got line: [%s]",buf);
|
|
if (strstr(buf, "skill:") == buf) {
|
|
skill_t *sk;
|
|
enum SKILLLEVEL slev;
|
|
strcpy(localbuf, buf + strlen("skill:"));
|
|
pp = strtok(localbuf, " ");
|
|
if (!pp) {
|
|
dblog("ERROR in playerfile. unknown skill level in this line:\n%s\n",buf);
|
|
goterror = B_TRUE;
|
|
}
|
|
slev = findskilllevbyname(pp);
|
|
|
|
pp += (strlen(pp) + 1);
|
|
if (!pp) {
|
|
dblog("ERROR in playerfile. missing skill name in this line:\n%s\n",buf);
|
|
goterror = B_TRUE;
|
|
}
|
|
sk = findskillbyname(pp);
|
|
if (sk) {
|
|
giveskilllev(lf, sk->id, slev);
|
|
} else {
|
|
dblog("ERROR in playerfile. unknown skill (%s) in this line:\n%s\n",pp, buf);
|
|
goterror = B_TRUE;
|
|
}
|
|
} else if (strstr(buf, "ob:") == buf) {
|
|
object_t *o;
|
|
strcpy(localbuf, buf + strlen("ob:"));
|
|
o = addob(lf->pack, localbuf);
|
|
if (o) {
|
|
identify(o);
|
|
} else {
|
|
dblog("ERROR in playerfile. unknown object in this line:\n%s\n",buf);
|
|
goterror = B_TRUE;
|
|
}
|
|
}
|
|
fgets(buf, BUFLEN, f);
|
|
}
|
|
return goterror;
|
|
}
|
|
|
|
int pctchance(int pct) {
|
|
if (rnd(1,100) <= pct) {
|
|
return B_TRUE;
|
|
}
|
|
return B_FALSE;
|
|
}
|
|
|
|
float pctof(float pct, float num) {
|
|
return ((pct / 100.0) * num);
|
|
}
|
|
|
|
|
|
// get a random number between min and max
|
|
int rnd(int min, int max) {
|
|
int res;
|
|
res = (rand() % (max - min + 1)) + min;
|
|
return res;
|
|
}
|
|
|
|
|
|
int real_roll(char *string, int wantmax) {
|
|
int ndice,nsides,bonus;
|
|
int roll;
|
|
texttodice(string, &ndice,&nsides,&bonus);
|
|
if (wantmax) {
|
|
roll = (ndice * nsides) + bonus;
|
|
} else {
|
|
roll = rolldie(ndice, nsides) + bonus;
|
|
}
|
|
return roll;
|
|
}
|
|
|
|
int roll(char *string) {
|
|
return real_roll(string, B_FALSE);
|
|
}
|
|
|
|
// get a random number
|
|
int rolldie(int ndice, int sides) {
|
|
int i;
|
|
int res = 0;
|
|
for (i = 0; i < ndice; i++) {
|
|
res += rnd(1,sides);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
int rollhitdice(lifeform_t *lf, int wantmax) {
|
|
flag_t *f;
|
|
int ndice, nsides = HITDIESIDES, plus = 0;
|
|
int myroll = 0;
|
|
int maxroll;
|
|
float pctofmax;
|
|
int db = B_FALSE;
|
|
int myfitness;
|
|
|
|
f = hasflag(lf->flags, F_HITDICE);
|
|
if (f) {
|
|
ndice = f->val[0];
|
|
if (f->val[1] > 0) {
|
|
plus = f->val[1];
|
|
}
|
|
if (f->val[2] > 0) {
|
|
nsides = f->val[2];
|
|
}
|
|
} else {
|
|
ndice = 1;
|
|
plus = 0;
|
|
}
|
|
|
|
maxroll = (ndice * nsides) + plus;
|
|
|
|
if (wantmax) {
|
|
myroll = maxroll;
|
|
} else {
|
|
myroll = rolldie(ndice, nsides) + plus;
|
|
}
|
|
|
|
if (db) dblog("TOTAL: %d",myroll);
|
|
|
|
// modify for fitness/con
|
|
if (isplayer(lf)) {
|
|
pctofmax = ((float)myroll / (float) maxroll) * 100.0;
|
|
myfitness = getattr(lf, A_CON);
|
|
if (pctofmax < myfitness) {
|
|
myroll = pctof(myfitness, maxroll);
|
|
if (db) dblog(" -> modified by fitness to: %d",myroll);
|
|
}
|
|
}
|
|
|
|
limit(&myroll, 1, NA);
|
|
return myroll;
|
|
}
|
|
|
|
int rollmpdice(lifeform_t *lf, int wantmax) {
|
|
flag_t *f;
|
|
int ndice, plus,roll,i;
|
|
float mod;
|
|
flag_t *retflag[MAXCANDIDATES];
|
|
int nretflags;
|
|
|
|
if (lfhasflag(lf, F_NOSPELLS)) {
|
|
return 0;
|
|
}
|
|
|
|
f = hasflag(lf->flags, F_MPDICE);
|
|
if (f) {
|
|
ndice = f->val[0];
|
|
if (f->val[1] == NA) plus = 0;
|
|
else plus = f->val[1];
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
mod = 100 + getstatmod(lf, A_IQ);
|
|
|
|
if (wantmax) {
|
|
roll = (ndice * 4) + plus;
|
|
} else {
|
|
roll = rolldie(ndice, 4) + plus;
|
|
}
|
|
roll = pctof(mod, roll);
|
|
|
|
// modify via racial flags
|
|
getflags(lf->flags, retflag, &nretflags, F_MPMOD, F_NONE);
|
|
for (i = 0; i < nretflags; i++) {
|
|
roll += retflag[i]->val[0];
|
|
}
|
|
|
|
limit(&roll, 0, NA);
|
|
return roll;
|
|
}
|
|
|
|
/*
|
|
void sortlf(map_t *map) {
|
|
int donesomething;
|
|
int db = B_FALSE;
|
|
lifeform_t *l,*nextl;
|
|
int iter = 0;
|
|
|
|
// bubblesort
|
|
donesomething = B_TRUE;
|
|
dblog("doing sort...");
|
|
while (donesomething) {
|
|
donesomething = B_FALSE;
|
|
|
|
//dblog("ITER %d",iter++);
|
|
if (db) {
|
|
dblog("ITER %d",iter++);
|
|
for (l = map->lf ; l ; l = l->next) {
|
|
dblog("- %s (timespent= %d) (sorted=%d)", (l == player) ? "player" : l->race->name, l->timespent,l->sorted);
|
|
}
|
|
}
|
|
|
|
|
|
for (l = map->lf ; l->next ; l = nextl) {
|
|
nextl = l->next;
|
|
//if (!l->sorted && (l->timespent >= l->next->timespent) ) {
|
|
if (l->sorted) {
|
|
//dblog("skipping id %d %s, already sorted ",l->id, l->race->name);
|
|
continue;
|
|
}
|
|
|
|
if (l->timespent >= l->next->timespent) {
|
|
lifeform_t *temp;
|
|
//dblog("moving id %d %s upwards",l->id, l->race->name);
|
|
// remember next element
|
|
temp = l->next;
|
|
|
|
// remove this element from list
|
|
// don't bother checking if (l->next == NULL) as we know
|
|
// this won't be true due to the for loop condition
|
|
if (l->prev == NULL) {
|
|
// first
|
|
map->lf = l->next;
|
|
l->next->prev = NULL;
|
|
} else {
|
|
// not first
|
|
l->prev->next = l->next;
|
|
l->next->prev = l->prev;
|
|
}
|
|
|
|
// TESTING: re-add at correct position.
|
|
while (temp->next && (temp->next->timespent <= l->timespent)) {
|
|
//dblog("moving past %d %s (timespend=%d)...",temp->next->id, temp->next->race->name, temp->next->timespent);
|
|
temp = temp->next;
|
|
}
|
|
|
|
// re-add element afterwards
|
|
l->next = temp->next;
|
|
l->prev = temp;
|
|
temp->next = l;
|
|
if (l->next == NULL) {
|
|
map->lastlf = l;
|
|
} else {
|
|
l->next->prev = l;
|
|
}
|
|
|
|
l->sorted = B_TRUE;
|
|
|
|
donesomething = B_TRUE;
|
|
break;
|
|
} else {
|
|
l->sorted = B_TRUE;
|
|
}
|
|
}
|
|
}
|
|
//dblog("finished sort.");
|
|
|
|
// reset sorted var for next time
|
|
for (l = map->lf ; l->next ; l = l->next) {
|
|
l->sorted = B_FALSE;
|
|
}
|
|
|
|
// sanity check
|
|
if (player->next) {
|
|
assert(player->next->prev == player);
|
|
}
|
|
if (player->prev) {
|
|
assert(player->prev->next == player);
|
|
}
|
|
|
|
if (db) {
|
|
dblog("AFTER SORT:");
|
|
for (l = map->lf ; l ; l = l->next) {
|
|
// if (haslos(player, l->cell)) {
|
|
dblog("- %s (timespent= %d) (sorted=%d)", (l == player) ? "player" : l->race->name, l->timespent,l->sorted);
|
|
// }
|
|
}
|
|
}
|
|
|
|
}
|
|
*/
|
|
|
|
|
|
|
|
void timeeffectsworld(map_t *map, int updategametime) {
|
|
flag_t *retflag[MAXCANDIDATES];
|
|
int nretflags;
|
|
lifeform_t *l;
|
|
int db = B_FALSE;
|
|
object_t *o,*nexto;
|
|
int x,y;
|
|
long firstlftime;
|
|
enum SKILLLEVEL cartskill;
|
|
warning_t *w,*nextw;
|
|
|
|
//dblog("------ tick (time=%ld) ----", curtime);
|
|
|
|
cartskill = getskill(player, SK_CARTOGRAPHY);
|
|
|
|
// now go through the list and make the first element be 0
|
|
l = map->lf;
|
|
|
|
if (l) {
|
|
// if (db) dblog("first lf is: %s (id %ld)\n",l->race->name, l->id);
|
|
firstlftime = l->timespent;
|
|
assert(firstlftime >= 0);
|
|
} else {
|
|
dblog("no lifeforms on map!\n");
|
|
// no first lf!
|
|
firstlftime = 0;
|
|
}
|
|
|
|
//if (db) dblog("timespent = %d\n", timespent);
|
|
if (db) dblog("firstlftime = %d\n", firstlftime);
|
|
|
|
if (firstlftime > 0) {
|
|
if (db) dblog("making firstlf timespent = 0 (currently %d):", firstlftime);
|
|
//dumplf();
|
|
for (l = map->lf ; l ; l = l->next) {
|
|
//dblog("shuffling id %d %s timespent=%d -> %d",l->id,l->race->name, l->timespent, l->timespent - firstlftime);
|
|
l->timespent -= firstlftime;
|
|
assert(l->timespent >= 0);
|
|
/*
|
|
if (isplayer(l)) {
|
|
statdirty = B_TRUE;
|
|
}
|
|
*/
|
|
|
|
}
|
|
//dblog("after shuffle:");
|
|
//dumplf();
|
|
} else {
|
|
if (db) dblog("firstlf timespent is not greater than 0. no shuffle.");
|
|
}
|
|
|
|
timeleft += firstlftime;
|
|
// now do effects based on time...
|
|
while (timeleft >= TICK_INTERVAL) {
|
|
flag_t *f;
|
|
int i;
|
|
|
|
timeleft -= TICK_INTERVAL;
|
|
dblog("------ tick (time=%ld) ----", curtime);
|
|
|
|
// global time-based effects on map or map objects
|
|
for (y = 0; y < map->h; y++) {
|
|
for (x = 0; x < map->w; x++) {
|
|
cell_t *c;
|
|
c = getcellat(map, x, y);
|
|
if (c) {
|
|
object_t *pit;
|
|
|
|
if (cartskill <= PR_INEPT) {
|
|
// if you are inept at cartography, you will forget
|
|
// cells outside your view which are far away.
|
|
if (c->knowntime > 0) {
|
|
c->knowntime--;
|
|
if (c->knowntime <= 0) {
|
|
c->known = B_FALSE;
|
|
c->knowntime = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// writing fading?
|
|
if (c->writing && (c->writinglifetime > 0)) {
|
|
c->writinglifetime--;
|
|
if (c->writinglifetime <= 0) {
|
|
free(c->writing);
|
|
c->writing = NULL;
|
|
if (haslos(player, c)) {
|
|
if (c == player->cell) {
|
|
msg("The magical inscription beneath you vanishes.");
|
|
} else {
|
|
msg("A magical inscription nearby vanishes.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pit = hasobwithflagval(c->obpile, F_PIT, D_DOWN, NA, NA, NULL);
|
|
if (!pit) pit = hasob(c->obpile, OT_GRATINGFLOOR);
|
|
if (pit) {
|
|
obsfallthrough(c, pit);
|
|
}
|
|
|
|
|
|
// go through each object in the cell...
|
|
for (o = c->obpile->first ; o ; o = nexto) {
|
|
nexto = o->next;
|
|
assert(nexto != o);
|
|
|
|
checkflagpile(o->flags);
|
|
timeeffectsob(o);
|
|
} // end foreach object here
|
|
|
|
// expire light effects
|
|
if (c->littimer > 0) {
|
|
c->littimer--;
|
|
if (c->littimer == 0) {
|
|
c->lit = c->origlit;
|
|
}
|
|
}
|
|
|
|
// water/fire spreads...
|
|
if (doelementspread(c) && haslos(player, c)) {
|
|
needredraw = B_TRUE;
|
|
}
|
|
} // end if c
|
|
}
|
|
} // end for (y...
|
|
|
|
// now finish off water spread
|
|
noredraw = B_TRUE;
|
|
|
|
getflags(map->flags, retflag, &nretflags, F_NEWWATERDEPTH, F_NONE);
|
|
assert(nretflags < MAXCANDIDATES); // oooooooooooooo
|
|
for (i = 0; i < nretflags; i++) {
|
|
cell_t *c;
|
|
f = retflag[i];
|
|
c = getcellat(map, f->val[0], f->val[1]);
|
|
if (c) {
|
|
if (f->val[2] > DP_NONE) {
|
|
o = hasobwithflag(c->obpile, F_DEEPWATER);
|
|
if (!o) {
|
|
o = addobfast(c->obpile, OT_WATERDEEP);
|
|
}
|
|
}
|
|
setwaterdepth(c, f->val[2]);
|
|
}
|
|
}
|
|
killflagsofid(map->flags, F_NEWWATERDEPTH);
|
|
noredraw = B_FALSE;
|
|
|
|
|
|
// now handle effects on lifeforms and/or their objects
|
|
for (l = map->lf ; l ; l = l->next) {
|
|
checkflagpile(l->flags);
|
|
timeeffectslf(l);
|
|
}
|
|
|
|
// time out warnings
|
|
for (w = firstwarning ;w ; w = nextw) {
|
|
nextw = w->next;
|
|
if (w->lifetime > 0) {
|
|
w->lifetime--;
|
|
if (w->lifetime <= 0) killwarning(w);
|
|
}
|
|
}
|
|
|
|
//dblog("AFTER SORT AND ADJUST.....");
|
|
//dumplf();
|
|
} // end if timespent
|
|
|
|
if (updategametime) {
|
|
// inc game time
|
|
curtime += (firstlftime*(TIMECONST));
|
|
// don't let it get higher than 23:59
|
|
while (curtime >= DAYSECS) {
|
|
curtime -= DAYSECS;
|
|
}
|
|
|
|
// inc total gametime passed
|
|
gamesecs += firstlftime;
|
|
while (gamesecs >= DAYSECS) {
|
|
gamesecs -= DAYSECS;
|
|
gamedays++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
// if it's the player's turn, announce sun set/rise
|
|
if (isplayer(map->lf) && isoutdoors(map)) {
|
|
int h,m,s;
|
|
splittime(&h, &m, &s);
|
|
if (h == 6) {
|
|
msg("The sun is rising.");
|
|
} else if (h == 18) {
|
|
msg("The sun is setting.");
|
|
}
|
|
}
|
|
*/
|
|
|
|
if (db) dblog("cur time is %ld\n",curtime);
|
|
}
|
|
|
|
void usage(char *progname) {
|
|
printf("usage: %s [ -f playerfile ]\n",progname);
|
|
printf("\t-f xx\tReads player details from file xx.\n");
|
|
}
|
|
|