nexus/nexus.c

911 lines
20 KiB
C

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "ai.h"
#include "attack.h"
#include "io.h"
#include "flag.h"
#include "lf.h"
#include "map.h"
#include "move.h"
#include "nexus.h"
#include "objects.h"
#include "save.h"
#include "text.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;
command_t *firstcommand = NULL,*lastcommand = NULL;
race_t *firstrace = NULL,*lastrace = NULL;
job_t *firstjob = NULL,*lastjob = NULL;
skill_t *firstskill = NULL,*lastskill = NULL;
map_t *firstmap = NULL,*lastmap = NULL;
knowledge_t *knowledge = NULL, *lastknowledge = NULL;
hiddenname_t *firsthiddenname = NULL, *lasthiddenname = NULL;
// 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;
enum GAMEMODE gamemode = GM_FIRST;
long curtime = 0;
long timeleft = 0;
extern int statdirty;
int needredraw = B_TRUE;
int numdraws = 0;
int main(int argc, char **argv) {
int newworld = B_FALSE;
object_t *o;
char welcomemsg[BUFLEN];
atexit(cleanup);
// init params
if (init()) {
exit(1);
}
// load whatever maps are available
loadall();
// init graphics
initgfx();
// if no maps, make the initial level
if (!firstmap) {
newworld = B_TRUE;
addmap();
createmap(firstmap, 1, H_DUNGEON, NULL, findot(OT_STAIRSUP));
}
if (!knowledge) {
// populate scroll, potion, etc names
genhiddennames();
}
// if no player (ie. didn't load a game), add them
if (!player) {
char *user;
char pname[BUFLEN];
job_t *j;
char ch;
cell_t *where;
// ask for race
initprompt(&prompt, "Select your race:");
ch = 'a';
for (j = firstjob ; j ; j = j->next) {
addchoice(&prompt, ch++, j->name, NULL, j);
}
j = NULL;
while (!j) {
getchoice(&prompt);
j = prompt.result;
}
// find staircase
where = findobinmap(firstmap, OT_STAIRSUP);
assert(where);
// make sure no lifeforms are there
if (where->lf) {
killlf(where->lf);
}
// add player
real_addlf(where, R_HUMAN, 1, C_PLAYER); // this will assign 'player'
user = getenv("USER");
if (user) {
addflag(player->flags, F_NAME, NA, NA, NA, getenv("USER"));
} else {
addflag(player->flags, F_NAME, NA, NA, NA, "Anonymous");
}
givejob(player, j->id);
// player needs hunger
addflag(player->flags, F_HUNGER, 0, NA, NA, NULL);
getplayernamefull(pname);
sprintf(welcomemsg, "Greetings %s, welcome to %snexus!", pname, newworld ? "the new " : "");
// XXX testing
//addlf(getcellindir(player->cell, D_N), R_GOBLIN, 1);
// 00:00 - 23:59
curtime = rnd(0,86399);
} else {
sprintf(welcomemsg, "Welcome back!");
}
// start game - this will cause debug messages to now
// go to the log file instead of stdout.
gamemode = GM_GAMESTARTED;
timeleft = 0; // reset game timer
// calculate initial light
calclight(player->cell->map);
// pre-calc line-of-sight for player
precalclos(player);
// show level
drawscreen();
msg("%s",welcomemsg);
more();
// MAIN LOOP
// basic flow is:
//
// donextturn() - process a turn for a lifeform
// turneffectslf() 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 -> timehappens();
donextturn(curmap);
// update lifeform structue to figure out who goes next
//timehappens(curmap);
// show level (if required)
//if (haslos(player, curmap->lf->cell)) {
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);
return B_FALSE;
}
celltype_t *addcelltype(int id, char *name, char glyph, int solid, int transparent, enum MATERIAL mat) {
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 = glyph;
a->solid = solid;
a->transparent = transparent;
a->material = findmaterial(mat);
a->flags = addflagpile(NULL, NULL);
return a;
}
command_t *addcommand(enum COMMAND id, char ch, char *desc) {
command_t *a;
// add to the end of the list
if (firstcommand == NULL) {
firstcommand = malloc(sizeof(command_t));
a = firstcommand;
a->prev = NULL;
} else {
// go to end of list
a = lastcommand;
a->next = malloc(sizeof(command_t));
a->next->prev = a;
a = a->next;
}
lastcommand = a;
a->next = NULL;
// set props
a->id = id;
a->ch = ch;
a->desc = strdup(desc);
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 death
if (lf->hp <= 0) {
// die!
die(lf);
continue;
}
// check for object death
removedeadobs(lf->pack);
}
// 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) {
if (!player->alive) {
gameover = B_TRUE;
}
}
void cleanup(void) {
fclose(logfile);
cleanupgfx();
// free maps
// free knowledge
// free brands
// free obtypes
// free objects
// free materials
// free races
}
void donextturn(map_t *map) {
lifeform_t *who;
int db = B_FALSE;
who = map->lf;
if (db) dblog("**** donextturn for: id %d %s", who->id, who->race->name);
turneffectslf(who);
// calculate light
calclight(map);
// pre-calculate line of sight for this lifeform
precalclos(who);
if (isplayer(who) || cansee(player, who)) {
needredraw = B_TRUE;
drawscreen();
}
// update gun targets
autotarget(who);
assert(who->timespent == 0);
// 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;
if (donormalmove) {
// paralyzed etc?
if (isimmobile(who)) {
rest(who, B_FALSE);
donormalmove = B_FALSE;
}
}
// resting?
if (donormalmove) {
f = hasflag(who->flags, F_RESTING);
if (f) {
rest(who, B_TRUE);
donormalmove = B_FALSE;
}
}
if (donormalmove) {
if (isplayer(who)) {
drawcursor();
// find out what player wants to do
handleinput();
} else {
// do ai move
aimove(who);
}
}
}
}
}
if (hasflag(player->flags, F_RESTING)) {
// ooo is this right ?
needredraw = B_FALSE;
} else if (isdead(who) || cansee(player, who)) {
needredraw = B_TRUE;
}
// 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.
timeeffectsworld(player->cell->map); // in case the player changed levels!
}
celltype_t *findcelltype(int id) {
celltype_t *ct;
for (ct = firstcelltype; ct ; ct = ct->next) {
if (ct->id == id) {
return ct;
}
}
return NULL;
}
char *getdirname(int dir) {
switch (dir) {
case D_N:
return "North";
case D_E:
return "East";
case D_S:
return "South";
case D_W:
return "West";
case D_UP:
return "up";
case D_DOWN:
return "down";
case D_UNKNOWN:
return "D_UNKNOWN";
case D_NONE:
return "D_NONE";
case DC_N:
return "North";
case DC_NE:
return "Northeast";
case DC_E:
return "East";
case DC_SE:
return "Southeast";
case DC_S:
return "South";
case DC_SW:
return "Southwest";
case DC_W:
return "West";
case DC_NW:
return "Northwest";
}
return "?errordir?";
}
void getrarity(int depth, int *min, int *max, int range) {
int mid;
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) {
// random numbers
srand(time(NULL));
gamemode = GM_INIT;
initcommands();
initobjects();
initskills();
initjobs();
initrace();
// cell types
addcelltype(CT_WALL, "rock wall", '#', B_SOLID, B_OPAQUE, MT_STONE);
addcelltype(CT_ROOMWALL, "rock wall", '#', B_SOLID, B_OPAQUE, MT_STONE);
addcelltype(CT_CORRIDOR, "rock floor", '.', B_EMPTY, B_TRANS, MT_STONE);
addcelltype(CT_LOOPCORRIDOR, "rock floor", 'L', B_EMPTY, B_TRANS, MT_STONE);
addcelltype(CT_ROOM, "rock floor", '.', B_EMPTY, B_TRANS, MT_STONE);
//addcelltype(CT_DOOROPEN, "wooden door", '-', B_EMPTY, B_TRANS, MT_WOOD);
//addcelltype(CT_DOORCLOSED, "wooden door", '+', B_SOLID, B_OPAQUE, MT_WOOD);
gamemode = GM_VALIDATION;
if (validateobs()) {
return B_TRUE;
}
if (validateraces()) {
return B_TRUE;
}
// open log file
logfile = fopen("log.txt","wt");
fprintf(logfile, "\n\n\n====== NEW LOGFILE ====\n");
return B_FALSE;
}
void initcommands(void) {
// Actions
addcommand(CMD_UP, '<', "Go up stairs.");
addcommand(CMD_DOWN, '>', "Go down stairs, enter a shop/portal.");
addcommand(CMD_REST, '.', "Rest once.");
addcommand(CMD_PICKUP, ',', "Pick up something from the ground.");
addcommand(CMD_CLOSE, 'c', "Close a door.");
addcommand(CMD_DROP, 'd', "Drop an item.");
addcommand(CMD_DROPMULTI, 'D', "Drop multiple items.");
addcommand(CMD_EAT, 'e', "Eat something.");
addcommand(CMD_EAT, 'E', "Enhance your skills.");
addcommand(CMD_MAGIC, 'm', "Use magic or abilities.");
addcommand(CMD_MEMMAGIC, 'M', "Memorise a magic shortcut");
addcommand(CMD_OPERATE, 'o', "Operate a tool/wand/device.");
addcommand(CMD_PICKLOCK, 'p', "Pick a lock.");
addcommand(CMD_POUR, 'P', "Pour a potion onto something.");
addcommand(CMD_QUAFF, 'q', "Quaff (drink) a potion.");
addcommand(CMD_READ, 'r', "Read a scroll/book.");
addcommand(CMD_RESTFULL, 'R', "Rest until healed, or train your skills.");
addcommand(CMD_TAKEOFF, 'T', "Take off an item of clothing/jewelery.");
addcommand(CMD_WEILD, 'w', "Weild a weapon.");
addcommand(CMD_WEAR, 'W', "Wear an item of clothing/jewelery.");
// Firearms
addcommand(CMD_FIRE, 'f', "Fire your firearm/bow at your current target.");
addcommand(CMD_FIRENEW, 'F', "Fire your firearm/bow at a new target.");
addcommand(CMD_AIM, 'a', "Aim your current firearm/bow at a new target.");
// Information
addcommand(CMD_HELP, '?', "Display this text.");
addcommand(CMD_INFOPLAYER, '@', "Display player stats.");
addcommand(CMD_INFOARMOUR, ']', "Display player armour.");
addcommand(CMD_FORCEATTACK, 'A', "Force an attack in a given direction.");
addcommand(CMD_LOOKHERE, ':', "Look at current cell.");
addcommand(CMD_LOOKAROUND, '/', "Look at a remote cell.");
addcommand(CMD_INFOKNOWLEDGE, '\\', "Display known items.");
addcommand(CMD_MSGHIST, '|', "Display message history.");
addcommand(CMD_INV, 'i', "Display your inventory.");
// GAME FUNCTIONS
addcommand(CMD_QUIT, 'Q', "Quit the game.");
addcommand(CMD_SAVEQUIT, 'S', "Save and quit the game.");
sortcommands();
}
int isplayerturn(void) {
if (!player) return B_FALSE;
if (player->cell->map->lf->controller == C_PLAYER) {
return B_TRUE;
}
return B_FALSE;
}
int limit(int *what, int min, int max) {
int limited = B_FALSE;
if (*what < min) {
*what = min;
limited = B_TRUE;
}
if (*what > max) {
*what = max;
limited = B_TRUE;
}
return limited;
}
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 roll(char *string) {
int ndice,nsides,bonus;
int roll;
texttodice(string, &ndice,&nsides,&bonus);
roll = rolldie(ndice, nsides) + bonus;
return roll;
}
// 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) {
flag_t *f;
int ndice, plus;
int roll = 0;
int i;
float mod;
int db = B_TRUE;
f = hasflag(lf->flags, F_HITDICE);
if (f) {
ndice = f->val[0];
if (f->val[1] == NA) plus = 0;
else plus = f->val[1];
} else {
ndice = 1;
plus = 0;
}
if (db && isplayer(lf)) dblog("rollhitdice() - rolling %dd4 + %d",ndice,plus);
mod = getstatmod(lf, A_CON);
if (mod > 0) mod *= 2;
if (db && isplayer(lf)) dblog("rollhitdice() - mod is +%0.0f%%",mod);
if (ndice == 0) {
int thisroll;
// just the bonus
thisroll = plus;
thisroll = thisroll + (int)((float)thisroll * (mod/100));
if (thisroll < 1) thisroll = 1;
roll += thisroll;
} else {
for (i = 0; i < ndice; i++) {
int thisroll;
thisroll = rolldie(1, 4) + plus;
if (thisroll < 1) thisroll = 1;
if (db && isplayer(lf)) dblog("rollhitdice() ---- die %d/%d == %d",i+1,ndice,thisroll);
roll += thisroll;
}
}
if (db && isplayer(lf)) dblog("TOTAL: %d",roll);
roll = roll + (int)((float)roll * (mod/100));
if (db && isplayer(lf)) dblog(" -> modified to: %d",roll);
return roll;
}
int rollmpdice(lifeform_t *lf) {
flag_t *f;
int ndice, plus;
int roll;
float mod;
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 = getstatmod(lf, A_IQ);
if (mod > 0) mod *= 2;
roll = rolldie(ndice, 4) + plus;
roll = roll + (int)((float)roll * (mod/100));
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 sortcommands(void) {
command_t *c;
int donesomething = B_TRUE;
while (donesomething) {
donesomething = B_FALSE;
for (c = firstcommand ; c->next ; c = c->next) {
// move up one position if required.
if (c->ch > c->next->ch) {
command_t *temp;
// remember next element
temp = c->next;
// remove this element from list
if (c->prev == NULL) {
// first
firstcommand = c->next;
c->next->prev = NULL;
} else {
// not first
c->prev->next = c->next;
c->next->prev = c->prev;
}
// re-add element afterwards
c->next = temp->next;
c->prev = temp;
temp->next = c;
if (c->next == NULL) {
lastcommand = c;
} else {
c->next->prev = c;
}
// mark as done.
donesomething = B_TRUE;
break;
}
}
}
}
void timeeffectsworld(map_t *map) {
lifeform_t *l;
int db = B_FALSE;
object_t *o,*nexto;
int x,y;
long firstlftime;
// 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) {
timeleft -= TICK_INTERVAL;
// 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) {
// go through each object in the cell...
for (o = c->obpile->first ; o ; o = nexto) {
nexto = o->next;
timeeffectsob(o);
} // end foreach object here
// expire light effects
if (c->littimer > 0) {
c->littimer--;
if (c->littimer == 0) {
c->lit = c->origlit;
}
}
}
}
} // end for (y...
// now handle effects on lifeforms and/or their objects
for (l = map->lf ; l ; l = l->next) {
timeeffectslf(l);
}
//dblog("AFTER SORT AND ADJUST.....");
//dumplf();
} // end if timespent
// inc game time
curtime += firstlftime;
if (db) dblog("cur time is %ld\n",curtime);
}