#include #include #include #include #include #include #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; int gamestarted = B_FALSE; 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); // 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. gamestarted = B_TRUE; 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) || haslos(player, who->cell)) { needredraw = B_TRUE; } // 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) || haslos(player, who->cell)) { 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"; } 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)); 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); 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_MAGIC, 'm', "Use magic or abilities."); 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."); 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; } // 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; 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; } roll = rolldie(ndice, 4) + plus; 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); for (o = l->pack->first ; o ; o = nexto) { nexto = o->next; timeeffectsob(o); } } //dblog("AFTER SORT AND ADJUST....."); //dumplf(); } // end if timespent // inc game time curtime += firstlftime; if (db) dblog("cur time is %ld\n",curtime); }