From f5a7d56a949ee7058e2e92f3c0cebbc9fc33b534 Mon Sep 17 00:00:00 2001 From: Martin Read Date: Sun, 9 Mar 2014 19:56:27 +0000 Subject: [PATCH] Just a boring ball-of-mud checkpoint --- Makefile | 10 +- core.hh | 17 +- default.permons | 89 ++++++++++ display-nc.cc | 111 +++++++------ display.hh | 1 + log.cc | 454 +++++++++++++++++++++++++++++++++++----------------- map.cc | 10 +- map.hh | 168 +++++++++++++------ monsters.cc | 78 +++++++-- monsters.hh | 4 +- notify-local-tty.cc | 6 + notify.hh | 1 + objects.cc | 91 +++++++++-- objects.hh | 6 +- permon.hh | 71 ++++---- player.hh | 5 +- pmon_comp | 10 ++ 17 files changed, 814 insertions(+), 318 deletions(-) diff --git a/Makefile b/Makefile index 3529790..9c22d89 100644 --- a/Makefile +++ b/Makefile @@ -17,10 +17,10 @@ MINVERS:=0 PATCHVERS:=0 COMMON_CFLAGS:=-Wall -Wwrite-strings -Wunreachable-code -Wformat -Werror=format-security -fstack-protector --param=ssp-buffer-size=4 -DMAJVERS=$(MAJVERS) -DMINVERS=$(MINVERS) -DPATCHVERS=$(PATCHVERS)-std=gnu11 -D_FORTIFY_SOURCE=2 -I$(srcdir) COMMON_CXXFLAGS:=-Wall -Wwrite-strings -Wno-unused-but-set-variable -Wredundant-decls -Wunreachable-code -Wformat -Werror=format-security -fstack-protector --param=ssp-buffer-size=4 -DMAJVERS=$(MAJVERS) -DMINVERS=$(MINVERS) -DPATCHVERS=$(PATCHVERS) -std=gnu++11 -D_FORTIFY_SOURCE=2 -I$(srcdir) -PRODUCTION_CFLAGS:=$(COMMON_CFLAGS) -DEVELOPMENT_CFLAGS:=$(COMMON_CFLAGS) -g -O2 -Werror -PRODUCTION_CXXFLAGS:=$(COMMON_CXXFLAGS) -DEVELOPMENT_CXXFLAGS:=$(COMMON_CXXFLAGS) -g -O2 -Werror +PRODUCTION_CFLAGS:=$(COMMON_CFLAGS) -O2 +DEVELOPMENT_CFLAGS:=$(COMMON_CFLAGS) -g -O1 -Werror +PRODUCTION_CXXFLAGS:=$(COMMON_CXXFLAGS) -O2 +DEVELOPMENT_CXXFLAGS:=$(COMMON_CXXFLAGS) -g -O1 -Werror LIBS=-lpanelw -lncursesw -lxdg-basedir -lm ARCHIVEDIR:=$(GAME)-$(MAJVERS).$(MINVERS).$(PATCHVERS) ARCHIVENAME:=$(GAME)_$(MAJVERS).$(MINVERS).$(PATCHVERS) @@ -99,7 +99,7 @@ combat.o: $(srcdir)/combat.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir) coord.o: $(srcdir)/coord.cc $(srcdir)/coord.hh -display-nc.o: $(srcdir)/display-nc.cc $(srcdir)/$(GAME).hh $(srcdir)/display.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh +display-nc.o: $(srcdir)/display-nc.cc $(srcdir)/$(GAME).hh $(srcdir)/display.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh $(srcdir)/map.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh $(srcdir)/permon.hh $(srcdir)/permobj.hh log.o: $(srcdir)/log.cc $(srcdir)/objects.hh $(srcdir)/monsters.hh $(srcdir)/player.hh $(srcdir)/map.hh $(srcdir)/util.h diff --git a/core.hh b/core.hh index 3f47981..9e88c8b 100644 --- a/core.hh +++ b/core.hh @@ -84,14 +84,15 @@ enum Noisiness */ enum Gamecolour { + Gcol_nocolour = -1, /* l_cyan is the last "core" colour. Colours after it may alias to one * of the core colours depending on the nature of your display. */ - Gcol_l_grey, Gcol_d_grey, Gcol_red, Gcol_blue, + Gcol_l_grey = 0, Gcol_d_grey, Gcol_red, Gcol_blue, Gcol_green, Gcol_purple, Gcol_brown, Gcol_cyan, Gcol_white, Gcol_l_red, Gcol_l_blue, Gcol_l_green, Gcol_l_purple, Gcol_yellow, Gcol_l_cyan, /* Fancy colours now! */ - Gcol_iron, Gcol_gold, Gcol_silver, + Gcol_iron, Gcol_gold, Gcol_silver, Gcol_blood, Gcol_pus, /* UI customizable colours */ Gcol_prio_low, Gcol_prio_normal, Gcol_prio_alert, Gcol_prio_warn, Gcol_prio_bug }; @@ -131,7 +132,7 @@ enum Death { DEATH_LASH, DEATH_RIBBONS }; -/*! \brief Fell powers that might influence the Princess's fate +/*! \brief Fell powers from forbidden places */ enum Fell_powers { FePo_iron, @@ -167,6 +168,16 @@ typedef uint32_t Obj_handle; class Mon; typedef uint32_t Mon_handle; +enum Decal_tag +{ + NO_DECAL = -1, + Decal_blood, + Decal_ichor, + Decal_pus +}; +#define MAX_DECAL (Decal_pus) +#define NUM_DECALS (MAX_DECAL + 1) + #define NO_POBJ (-1) #define NO_PMON (-1) extern const Obj_handle NO_OBJ; diff --git a/default.permons b/default.permons index 2071cee..1dd48ee 100644 --- a/default.permons +++ b/default.permons @@ -21,6 +21,23 @@ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +monster adventurer +ascii '@' +utf8 "@" +desc A typical morally stunted specimen of the dungeoneering class. +rarity 100 +power 1 +hp 1 +mtohit 0 +mdam 1 +defence 1 +exp 0 +speed 1 +STUPID +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE + monster newt ascii 'n' utf8 "n" @@ -37,6 +54,9 @@ speed 0 STUPID SMALL QUADRUPED +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE # rodents monster rat @@ -55,6 +75,9 @@ speed 2 STUPID SMALL QUADRUPED +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE # canids monster wolf @@ -72,6 +95,9 @@ defence 6 exp 15 speed 2 QUADRUPED +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE # Serpents monster snake @@ -89,6 +115,9 @@ experience 40 speed 2 STUPID SERPENTINE +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE # thugs monster thug @@ -105,6 +134,9 @@ defence 4 experience 5 speed 1 HUMANOID +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE monster goon desc Really good at pushing people's faces through the backs of their heads. @@ -120,6 +152,9 @@ defence 8 experience 10 speed 1 HUMANOID +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE # hunters monster hunter @@ -140,6 +175,9 @@ experience 50 speed 1 ARCHER HUMANOID +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE # fighters monster duellist @@ -157,6 +195,9 @@ experience 130 speed 1 SMART HUMANOID +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE monster warlord desc A truly exceptional warrior, strong of arm and fleet of foot. @@ -173,6 +214,9 @@ experience 400 speed 2 SMART HUMANOID +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE # goblins monster goblin @@ -189,6 +233,9 @@ defence 3 experience 3 speed 1 HUMANOID +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE # elves monster bad elf @@ -207,6 +254,9 @@ experience 15 speed 2 SMART HUMANOID +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE # Trolls monster troll @@ -225,6 +275,9 @@ speed 1 STUPID HUMANOID HUGE +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE # Huge humanoids monster giant @@ -243,6 +296,9 @@ speed 1 STUPID HUMANOID HUGE +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE monster giant jarl desc A rarity among giants, this one has used its great strength and unusually sharp wits to persuade its dimmer brethren to obey. @@ -259,6 +315,9 @@ experience 1000 speed 1 HUMANOID HUGE +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE # wizards monster wizard @@ -281,6 +340,9 @@ speed 1 SMART MAGICIAN HUMANOID +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE monster archmage plural archmagi @@ -304,6 +366,9 @@ SMART MAGICIAN RESIST_ELEC HUMANOID +MADE_OF_MEAT +CONTAINS_BLOOD +LEAVES_CORPSE # zombie monster zombie @@ -325,6 +390,7 @@ RESIST_COLD RESIST_POIS RESIST_NECR HUMANOID +MADE_OF_MEAT # Wraiths monster wraith @@ -374,6 +440,7 @@ RESIST_POIS RESIST_NECR MAGICIAN HUMANOID +MADE_OF_BONE monster master lich plural master liches @@ -400,6 +467,7 @@ RESIST_POIS RESIST_NECR MAGICIAN HUMANOID +MADE_OF_BONE # Vampires monster vampire @@ -421,6 +489,8 @@ RESIST_COLD RESIST_POIS RESIST_NECR HUMANOID +MADE_OF_MEAT +CONTAINS_BLOOD # 4th tier demons monster fire imp @@ -440,6 +510,7 @@ DEMONIC RESIST_FIRE FLYING HUMANOID +MADE_OF_MEAT # 3rd tier demons monster demon @@ -459,6 +530,7 @@ SMART DEMONIC RESIST_FIRE HUMANOID +MADE_OF_MEAT # 2nd tier demons monster defiler @@ -479,6 +551,7 @@ DEMONIC MAGICIAN RESIST_POIS AMORPHOUS +MADE_OF_GOO # 1st tier demons monster putrid emissary @@ -498,6 +571,9 @@ SMART DEMONIC RESIST_POIS HUMANOID +MADE_OF_MEAT +CONTAINS_PUS +BURSTS_ON_DEATH monster tormentor desc The faceless violet-skinned figure before you wears a strangely cut garment of pale, supple leather which would be far beyond the limits of decency if there were any indecent parts to reveal. With one hand it beckons to you, while from the other it trails a long whip of the same leather as its garment. @@ -516,6 +592,7 @@ SMART DEMONIC RESIST_POIS HUMANOID +MADE_OF_MEAT monster iron lord desc This loyal and unwavering servant of Verant, Great Smith of the Hells, bears arms and armour from its master's forges. Fear the iron lord! @@ -537,6 +614,8 @@ RESIST_FIRE RESIST_COLD RESIST_SLAM HUMANOID +MADE_OF_MEAT +CONTAINS_BLOOD monster centaur desc A strange magical hybrid of horse and man. @@ -552,6 +631,9 @@ defence 10 experience 50 speed 2 CENTAUROID +MADE_OF_MEAT +LEAVES_CORPSE +CONTAINS_BLOOD monster ice monster desc A ponderous, shambling half-humanoid figure of ice and snow. @@ -573,6 +655,7 @@ speed 0 RESIST_COLD ARCHER HUMANOID +MADE_OF_ICE monster dragon desc A bulky beast of scales and fangs and fumes, capable of spewing searing flames to roast its enemies and rumoured to have a preference for char-grilled princess. @@ -595,6 +678,9 @@ RESIST_FIRE ARCHER QUADRUPED HUGE +MADE_OF_MEAT +LEAVES_CORPSE +CONTAINS_BLOOD monster moondrake desc A bat-winged serpent with eyes that shimmer like quicksilver and a breath that chills the living to the bone. @@ -619,3 +705,6 @@ RESIST_NECR FLYING ARCHER SERPENTINE +MADE_OF_MEAT +LEAVES_CORPSE +CONTAINS_BLOOD diff --git a/display-nc.cc b/display-nc.cc index 5e8699b..1716361 100644 --- a/display-nc.cc +++ b/display-nc.cc @@ -135,6 +135,8 @@ Attr_wrapper colour_data[1 + LAST_COLOUR] = { 0, Gcol_cyan }, /* Iron = dark cyan */ { A_BOLD, Gcol_brown }, /* Gold = yellow */ { A_BOLD, Gcol_l_grey }, /* Silver = white */ + { 0, Gcol_red }, /* Blood = red */ + { A_BOLD, Gcol_yellow }, /* Pus = yellow */ /* UI customizable colours */ { A_BOLD, Gcol_d_grey }, { 0, Gcol_l_grey }, @@ -143,6 +145,14 @@ Attr_wrapper colour_data[1 + LAST_COLOUR] = { A_BOLD, Gcol_blue } }; +/*! \brief Game colours for decals */ +Gamecolour decal_colours[NUM_DECALS] = +{ + Gcol_red, + Gcol_l_blue, + Gcol_yellow +}; + /*! \brief ncursesw objects for drawing terrain */ cchar_t terrain_tiles[NUM_TERRAINS]; @@ -333,6 +343,34 @@ static void draw_world(void) { mvwchgat(world_window, i, j, 1, A_BOLD, Gcol_d_grey, nullptr); } + else if (c != u.pos) + { + Obj_handle obj = lvl.obj_at(c); + Mon_handle mon = lvl.mon_at(c); + Decal_tag decal_id = lvl.decal_at(c); + if (mon != NO_MON) + { + // FUTURE: handle colour-customized monsters + } + else if (obj != NO_OBJ) + { + Obj const *optr = obj_snap(obj); + if (optr->po_ref == PO_CORPSE) + { + mvwchgat(world_window, i, j, 1, + colour_data[permons[optr->meta[0]].colour].attr, + colour_data[permons[optr->meta[0]].colour].cpair, + nullptr); + } + } + else if (decal_id != NO_DECAL) + { + mvwchgat(world_window, i, j, 1, + colour_data[decal_colours[decal_id]].attr, + colour_data[decal_colours[decal_id]].cpair, + nullptr); + } + } front_buffer[i][j] = back_buffer[i][j]; } } @@ -667,7 +705,13 @@ void print_msg(char const *fmt, ...) void print_msg(Msg_prio prio, char const *fmt, ...) { va_list ap; + char *s; + int i; + int j; va_start(ap, fmt); + s = (char *) malloc(512); + i = vasprintf(&s, fmt, ap); + va_end(ap); switch (prio) { case Msg_prio::Low: @@ -686,15 +730,17 @@ void print_msg(Msg_prio prio, char const *fmt, ...) wattr_set(message_window, colour_data[Gcol_prio_bug].attr, colour_data[Gcol_prio_bug].cpair, nullptr); break; } - vw_printw(message_window, fmt, ap); - wattr_set(message_window, colour_data[Gcol_prio_normal].attr, colour_data[Gcol_prio_normal].cpair, nullptr); - va_end(ap); + if (j > 50) + { + /* TODO catch this */ + } + waddstr(message_window, s); #ifdef DEBUG_TO_STDERR - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); + fwrite(s, 1, j, stderr); #endif + wattr_set(message_window, colour_data[Gcol_prio_normal].attr, colour_data[Gcol_prio_normal].cpair, nullptr); display_update(); + free(s); } /*! \brief Set a message and whether 'nothing' should be listed in inventory @@ -1465,6 +1511,7 @@ void print_help(void) print_msg("? a scroll\n"); print_msg("! a potion\n"); print_msg("%% some food\n"); + print_msg("& a corpse\n"); print_msg("1-4 demons (smaller numbers are stronger)\n"); print_msg("\nMost other monsters are shown as letters.\n"); print_msg("\nThis is all the help you get. Good luck!\n"); @@ -1519,7 +1566,7 @@ static void run_main_menu(void) mvwprintw(fullscreen_window, 10, 1, "\n"); mvwprintw(fullscreen_window, 11, 1, "\n"); mvwprintw(fullscreen_window, 12, 1, "\n"); - mvwprintw(fullscreen_window, 13, 19, "Welcome, Princess. Remind me of your name?\n"); + mvwprintw(fullscreen_window, 13, 19, "Welcome. Remind me of your name?\n"); mvwprintw(fullscreen_window, 14, 1, "\n"); wmove(fullscreen_window, 14, 30); update_panels(); @@ -1627,7 +1674,7 @@ static void examine_square(Offset o) mon = lvl.mon_at(c); obj = lvl.obj_at(c); terr = lvl.terrain_at(c); - print_msg("%s\n", terrain_props[terr]); + print_msg("%s\n", terrain_props[terr].name); if ((mon != NO_MON) && mon_visible(mon)) { print_msg("%s\n%s\n", permons[monsters[mon].pm_ref].name, permons[monsters[mon].pm_ref].description); @@ -1687,7 +1734,7 @@ static void farlook(void) void welcome(void) { - print_msg("Welcome to the Abyss, Princess %s.\n", u.name); + print_msg("A welcome to you, shadowed and veiled %s.\n", u.name); print_msg("Press '?' for help.\n"); } @@ -1701,49 +1748,17 @@ void describe_object(Obj_handle obj) void print_obj_name(Obj_handle obj) { - Obj const *optr = obj_snap(obj); - Permobj const *poptr = permobjs + optr->po_ref; - if (optr->quan > 1) - { - print_msg("%d %s", optr->quan, poptr->plural); - } - else if (po_is_stackable(optr->po_ref)) - { - print_msg("1 %s", poptr->name); - } - else if ((poptr->poclass == POCLASS_WEAPON) || - (poptr->poclass == POCLASS_ARMOUR)) - { - print_msg("a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR); - } - else - { - print_msg("a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name); - } + char *s; + asprint_obj_name(&s, obj); + print_msg("%s", s); } void print_mon_name(Mon_handle mon, int article) { - if (permons[monsters[mon].pm_ref].name[0] == '\0') - { - print_msg("GROB THE VOID (%d)", monsters[mon].pm_ref); - return; - } - switch (article) - { - case 0: /* a */ - print_msg("a%s %s", is_vowel(permons[monsters[mon].pm_ref].name[0]) ? "n" : "", permons[monsters[mon].pm_ref].name); - break; - case 1: /* the */ - print_msg("the %s", permons[monsters[mon].pm_ref].name); - break; - case 2: /* A */ - print_msg("A%s %s", is_vowel(permons[monsters[mon].pm_ref].name[0]) ? "n" : "", permons[monsters[mon].pm_ref].name); - break; - case 3: /* The */ - print_msg("The %s", permons[monsters[mon].pm_ref].name); - break; - } + char *s; + asprint_mon_name(&s, mon, article); + print_msg("%s", s); + free(s); } /* display-nc.cc */ diff --git a/display.hh b/display.hh index b524572..728dacd 100644 --- a/display.hh +++ b/display.hh @@ -57,6 +57,7 @@ extern void show_discoveries(void); extern void touch_one_screen(Coord c); extern void welcome(void); extern void print_obj_name(Obj_handle obj); +extern void print_mon_name(Mon_handle mon, int article); /* "I've changed things that need to be redisplayed" flags. */ extern bool hard_redraw; diff --git a/log.cc b/log.cc index 3bb7b44..ee90474 100644 --- a/log.cc +++ b/log.cc @@ -48,94 +48,10 @@ static void rebuild_mapmons(void); int datadir_fd; int confdir_fd; -/*! \brief Read configuration files. - * - * \todo Implement configuration files. - */ -void load_config(void) -{ - int i; - i = setup_dirs("com.blackswordsonics", "obumbrata", &datadir_fd, &confdir_fd); - if (i < 0) - { - perror("obumbrata: couldn't access configuration directories"); - exit(1); - } - // TODO write this function -} - -/*! \brief Rewrite configuration files. - * - * \todo Implement configuration files. - */ -int rewrite_config(void) -{ - // TODO write this function - return 0; -} - -/*! \brief Get a list of extant saved games and character names - * - * Fill in the map container *dest using character names - * as keys and filenames as values. +/*! \brief call system(cmd) and throw an exception on failure * - * \param dest Pointer to map container to fill with data - * \return Number of charname/filename pairs written to container - * \todo Basic implementation - * \todo Deal gracefully with a savefile going away before we open it - */ -int get_savefile_list(std::map *dest) -{ - // TODO write this function - return 0; -} - -/*! \brief Record the details of the Princess's death to the log + * \todo throw a proper exception */ -void log_death(Death d, char const *what) -{ - FILE *fp; - int fd; - fd = openat(datadir_fd, "obumbrata.log", O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR); - if (fd == -1) - { - return; - } - fp = fdopen(fd, "a"); - if (fp) - { - switch (d) - { - case DEATH_KILLED: - fprintf(fp, "%s was killed by %s.\n", u.name, what); - break; - case DEATH_KILLED_MON: - fprintf(fp, "%s was killed by a nasty %s.\n", u.name, what); - break; - case DEATH_BODY: - fprintf(fp, "%s's heart was stopped by %s.\n", u.name, what); - break; - case DEATH_AGILITY: - fprintf(fp, "%s's nerves were destroyed by %s.\n", u.name, what); - break; - case DEATH_LASH: - fprintf(fp, "%s tasted the lash one time too many.\n", u.name); - break; - case DEATH_RIBBONS: - fprintf(fp, "%s looked good in ribbons.\n", u.name); - break; - } - fprintf(fp, " %s died after %d ticks, with %d XP, on dungeon level %d.\n\n", u.name, game_tick, u.experience, depth); - fflush(fp); - fsync(fd); - fclose(fp); - } - else - { - close(fd); - } -} - void wrapped_system(char const *cmd) { int i = system(cmd); @@ -145,6 +61,10 @@ void wrapped_system(char const *cmd) } } +/*! \brief call fread(args) and throw an exception on failure + * + * \todo throw a proper exception + */ void wrapped_fread(void *buf, size_t size, size_t nmemb, FILE *fp) { size_t i = fread(buf, size, nmemb, fp); @@ -154,95 +74,113 @@ void wrapped_fread(void *buf, size_t size, size_t nmemb, FILE *fp) } } +/*! \brief Read an unsigned 32-bit integer in network byte order */ static inline void deserialize_uint32(FILE *fp, uint32_t *i) { wrapped_fread(i, 1, sizeof *i, fp); *i = ntohl(*i); } -static inline void deserialize_int32(FILE *fp, int32_t *i) +/*! \brief Write an unsigned 32-bit integer in network byte order */ +static inline void serialize_uint32(FILE *fp, uint32_t i) { - int32_t tmp; - wrapped_fread(&tmp, 1, sizeof tmp, fp); - *i = (int32_t) ntohl(tmp); + i = htonl(i); + fwrite(&i, 1, sizeof i, fp); } -static inline void deserialize_int(FILE *fp, int *i) +/*! \brief Read an signed 32-bit integer in network byte order */ +static inline void deserialize_int32(FILE *fp, int32_t *i) { int32_t tmp; wrapped_fread(&tmp, 1, sizeof tmp, fp); *i = (int32_t) ntohl(tmp); } -static inline void deserialize_coord(FILE *fp, Coord * c) +/*! \brief Write a signed 32-bit integer in network byte order */ +static inline void serialize_int32(FILE *fp, int32_t i) { - deserialize_int32(fp, &(c->y)); - deserialize_int32(fp, &(c->x)); + int32_t tmp = htonl(i); + fwrite(&tmp, 1, sizeof tmp, fp); } -static inline void serialize_uint32(FILE *fp, uint32_t i) +/*! \brief Read an int in network byte order */ +static inline void deserialize_int(FILE *fp, int *i) { - i = htonl(i); - fwrite(&i, 1, sizeof i, fp); + int32_t tmp; + wrapped_fread(&tmp, 1, sizeof tmp, fp); + *i = (int32_t) ntohl(tmp); } -static inline void serialize_int32(FILE *fp, int32_t i) +/*! \brief Write an int in network byte order */ +static inline void serialize_int(FILE *fp, int i) { int32_t tmp = htonl(i); fwrite(&tmp, 1, sizeof tmp, fp); } -static inline void serialize_int(FILE *fp, int i) +/*! \brief Read a Coord */ +static inline void deserialize_coord(FILE *fp, Coord * c) { - int32_t tmp = htonl(i); - fwrite(&tmp, 1, sizeof tmp, fp); + deserialize_int32(fp, &(c->y)); + deserialize_int32(fp, &(c->x)); } +/*! \brief Write a Coord */ static inline void serialize_coord(FILE *fp, Coord const& c) { serialize_int32(fp, c.y); serialize_int32(fp, c.x); } -static inline void serialize_monhandle(FILE *fp, Mon_handle m) +/*! \brief Read an Offset */ +static inline void deserialize_offset(FILE *fp, Offset * o) { - uint32_t tmp = htonl(m); - fwrite(&tmp, 1, sizeof tmp, fp); + deserialize_int32(fp, &(o->y)); + deserialize_int32(fp, &(o->x)); } -static void serialize_monster(FILE *fp, Mon const& mon) +/*! \brief Write an Offset */ +static inline void serialize_offset(FILE *fp, Coord const& o) { - serialize_monhandle(fp, mon.self); - serialize_int(fp, mon.pm_ref); - serialize_uint32(fp, mon.flags); - serialize_coord(fp, mon.pos); - serialize_coord(fp, mon.ai_lastpos); - serialize_int(fp, mon.hpmax); - serialize_int(fp, mon.hpcur); - serialize_int(fp, mon.mtohit); - serialize_int(fp, mon.rtohit); - serialize_int(fp, mon.defence); - serialize_int(fp, mon.mdam); - serialize_int(fp, mon.rdam); - serialize_int(fp, mon.next_summon); + serialize_int32(fp, o.y); + serialize_int32(fp, o.x); } -static inline void serialize_objhandle(FILE *fp, Obj_handle o) +/*! \brief Read a C-style string */ +static inline void deserialize_cstring(FILE *fp, char *str, uint32_t maxlen) { - uint32_t tmp = htonl(o); - fwrite(&tmp, 1, sizeof tmp, fp); + uint32_t i; + deserialize_uint32(fp, &i); + if (i >= maxlen) + { + // reading garbage + throw(-1); + } + else + { + memset(str, '\0', maxlen); + wrapped_fread(str, 1, i, fp); + } } -static inline void serialize_object(FILE *fp, Obj const& obj) +/*! \brief Write a C-style string */ +static inline void serialize_cstring(FILE *fp, char const *str, uint32_t lencheck) { - serialize_objhandle(fp, obj.self); - serialize_int(fp, obj.po_ref); - serialize_uint32(fp, obj.flags); - serialize_coord(fp, obj.pos); - serialize_int(fp, obj.quan); - serialize_int(fp, obj.durability); + size_t i = strlen(str); + if (i >= lencheck) + { + // welp! + serialize_uint32(fp, 8); + fwrite("(badstr)", 1, 8, fp); + } + else + { + serialize_uint32(fp, (uint32_t) i); + fwrite(str, 1, i, fp); + } } +/*! \brief Read a monster handle */ static inline void deserialize_monhandle(FILE *fp, Mon_handle *m) { uint32_t tmp; @@ -250,6 +188,14 @@ static inline void deserialize_monhandle(FILE *fp, Mon_handle *m) *m = htonl(tmp); } +/*! \brief Write a monster handle */ +static inline void serialize_monhandle(FILE *fp, Mon_handle m) +{ + uint32_t tmp = htonl(m); + fwrite(&tmp, 1, sizeof tmp, fp); +} + +/*! \brief Read an object handle */ static inline void deserialize_objhandle(FILE *fp, Obj_handle *o) { uint32_t tmp; @@ -257,16 +203,14 @@ static inline void deserialize_objhandle(FILE *fp, Obj_handle *o) *o = htonl(tmp); } -static inline void deserialize_object(FILE *fp, Obj *obj) +/*! \brief Write an object handle */ +static inline void serialize_objhandle(FILE *fp, Obj_handle o) { - deserialize_objhandle(fp, &(obj->self)); - deserialize_int(fp, &(obj->po_ref)); - deserialize_uint32(fp, &(obj->flags)); - deserialize_coord(fp, &(obj->pos)); - deserialize_int(fp, &(obj->quan)); - deserialize_int(fp, &(obj->durability)); + uint32_t tmp = htonl(o); + fwrite(&tmp, 1, sizeof tmp, fp); } +/*! \brief Read a monster */ static void deserialize_monster(FILE *fp, Mon * mon) { deserialize_monhandle(fp, &(mon->self)); @@ -284,11 +228,133 @@ static void deserialize_monster(FILE *fp, Mon * mon) deserialize_int(fp, &(mon->next_summon)); } +/*! \brief Write a monster */ +static void serialize_monster(FILE *fp, Mon const& mon) +{ + serialize_monhandle(fp, mon.self); + serialize_int(fp, mon.pm_ref); + serialize_uint32(fp, mon.flags); + serialize_coord(fp, mon.pos); + serialize_coord(fp, mon.ai_lastpos); + serialize_int(fp, mon.hpmax); + serialize_int(fp, mon.hpcur); + serialize_int(fp, mon.mtohit); + serialize_int(fp, mon.rtohit); + serialize_int(fp, mon.defence); + serialize_int(fp, mon.mdam); + serialize_int(fp, mon.rdam); + serialize_int(fp, mon.next_summon); +} + +/*! \brief Read an object */ +static void deserialize_object(FILE *fp, Obj *obj) +{ + deserialize_objhandle(fp, &(obj->self)); + deserialize_int(fp, &(obj->po_ref)); + deserialize_uint32(fp, &(obj->flags)); + deserialize_coord(fp, &(obj->pos)); + deserialize_int(fp, &(obj->quan)); + for (int i = 0; i < 4; ++i) + { + deserialize_uint32(fp, &(obj->meta[i])); + } + deserialize_int(fp, &(obj->durability)); +} + +/*! \brief Write an object */ +static void serialize_object(FILE *fp, Obj const& obj) +{ + serialize_objhandle(fp, obj.self); + serialize_int(fp, obj.po_ref); + serialize_uint32(fp, obj.flags); + serialize_coord(fp, obj.pos); + serialize_int(fp, obj.quan); + for (int i = 0; i < 4; ++i) + { + serialize_uint32(fp, obj.meta[i]); + } + serialize_int(fp, obj.durability); +} + +/*! \brief Read the player */ +static void deserialize_player(FILE *fp, Player *player) +{ + deserialize_cstring(fp, player->name, 17); + deserialize_monhandle(fp, &(player->mh)); + deserialize_coord(fp, &(player->pos)); + deserialize_int(fp, &(player->body)); + deserialize_int(fp, &(player->bdam)); + deserialize_int(fp, &(player->agility)); + deserialize_int(fp, &(player->adam)); + deserialize_int(fp, &(player->hpmax)); + deserialize_int(fp, &(player->hpcur)); + deserialize_int(fp, &(player->food)); + deserialize_int(fp, &(player->experience)); + deserialize_int(fp, &(player->defence)); + deserialize_int(fp, &(player->protection)); + deserialize_int(fp, &(player->leadfoot)); + deserialize_int(fp, &(player->withering)); + deserialize_int(fp, &(player->armourmelt)); + deserialize_int(fp, &(player->speed)); + for (int i = 0; i < DT_COUNT; ++i) + { + deserialize_uint32(fp, &(player->resistances[i])); + } + for (int i = 0; i < INVENTORY_SIZE; ++i) + { + deserialize_objhandle(fp, &(player->inventory[i])); + } + deserialize_objhandle(fp, &(player->weapon)); + deserialize_objhandle(fp, &(player->armour)); + deserialize_objhandle(fp, &(player->ring)); + for (int i = 0; i < TOTAL_FELL_POWERS; ++i) + { + deserialize_int(fp, &(player->sympathy[i])); + } +} + +/*! \brief Write the player */ +static void serialize_player(FILE *fp, Player const& player) +{ + serialize_cstring(fp, player.name, 17); + serialize_monhandle(fp, player.mh); + serialize_coord(fp, player.pos); + serialize_int(fp, player.body); + serialize_int(fp, player.bdam); + serialize_int(fp, player.agility); + serialize_int(fp, player.adam); + serialize_int(fp, player.hpmax); + serialize_int(fp, player.hpcur); + serialize_int(fp, player.food); + serialize_int(fp, player.experience); + serialize_int(fp, player.defence); + serialize_int(fp, player.protection); + serialize_int(fp, player.leadfoot); + serialize_int(fp, player.withering); + serialize_int(fp, player.armourmelt); + serialize_int(fp, player.speed); + for (int i = 0; i < DT_COUNT; ++i) + { + serialize_uint32(fp, player.resistances[i]); + } + for (int i = 0; i < INVENTORY_SIZE; ++i) + { + serialize_objhandle(fp, player.inventory[i]); + } + serialize_objhandle(fp, player.weapon); + serialize_objhandle(fp, player.armour); + serialize_objhandle(fp, player.ring); + for (int i = 0; i < TOTAL_FELL_POWERS; ++i) + { + serialize_int(fp, player.sympathy[i]); + } +} + +/*! \brief Save the game; set game_finished flag if successful */ void save_game(void) { FILE *fp; int fd; - uint32_t tmp; fd = openat(datadir_fd, "obumbrata.sav", O_WRONLY|O_TRUNC|O_CREAT, S_IRUSR | S_IWUSR); if (fd == -1) { @@ -300,12 +366,9 @@ void save_game(void) close(fd); return; } - /* Write out the player. */ - fwrite(&u, 1, sizeof u, fp); - tmp = htonl(depth); - fwrite(&tmp, 1, sizeof tmp, fp); - tmp = htonl(game_tick); - fwrite(&tmp, 1, sizeof tmp, fp); + serialize_int(fp, depth); + serialize_int(fp, game_tick); + serialize_player(fp, u); serialize_level(fp, &lvl); for (auto iter = monsters.begin(); iter != monsters.end(); ++iter) { @@ -327,8 +390,7 @@ void save_game(void) return; } -/*! \brief Monster map reinitialization after reload - */ +/*! \brief Reinitialize dungeon level monster handle arrays after reload */ static void rebuild_mapmons(void) { for (auto iter = monsters.begin(); iter != monsters.end(); ++iter) @@ -340,8 +402,7 @@ static void rebuild_mapmons(void) } } -/*! \brief Object map reinitialization after reload - */ +/*! \brief Reinitialize dungeon level object handle arrays after reload */ static void rebuild_mapobjs(void) { for (auto iter = objects.begin(); iter != objects.end(); ++iter) @@ -353,6 +414,7 @@ static void rebuild_mapobjs(void) } } +/*! \brief Reload and unlink a saved game */ int load_game(void) { int fd; @@ -367,11 +429,9 @@ int load_game(void) { return -1; } - wrapped_fread(&u, 1, sizeof u, fp); - wrapped_fread(&depth, 1, sizeof depth, fp); - depth = ntohl(depth); - wrapped_fread(&game_tick, 1, sizeof game_tick, fp); - game_tick = ntohl(game_tick); + deserialize_int(fp, &depth); + deserialize_int(fp, &game_tick); + deserialize_player(fp, &u); deserialize_level(fp, &lvl); while (1) { @@ -405,6 +465,7 @@ int load_game(void) } rebuild_mapobjs(); rebuild_mapmons(); + u.mptr = mon_snapv(u.mh); fclose(fp); game_finished = false; look_around_you(); @@ -417,6 +478,9 @@ int load_game(void) return 0; } +/*! \brief Write human-readable snapshot of the player character + * + * \todo Sanitize the player character's name, or the filename, somewhere */ void write_char_dump(void) { FILE *fp; @@ -464,5 +528,93 @@ void write_char_dump(void) fclose(fp); } +/*! \brief Read configuration files. + * + * \todo Implement configuration files. + */ +void load_config(void) +{ + int i; + i = setup_dirs("com.blackswordsonics", "obumbrata", &datadir_fd, &confdir_fd); + if (i < 0) + { + perror("obumbrata: couldn't access configuration directories"); + exit(1); + } + // TODO write this function +} + +/*! \brief Rewrite configuration files. + * + * \todo Implement configuration files. + */ +int rewrite_config(void) +{ + // TODO write this function + return 0; +} + +/*! \brief Get a list of extant saved games and character names + * + * Fill in the map container *dest using character names + * as keys and filenames as values. + * + * \param dest Pointer to map container to fill with data + * \return Number of charname/filename pairs written to container + * \todo Basic implementation + * \todo Deal gracefully with a savefile going away before we open it + */ +int get_savefile_list(std::map *dest) +{ + // TODO write this function + return 0; +} + +/*! \brief Record the details of the PC's death to the log + */ +void log_death(Death d, char const *what) +{ + FILE *fp; + int fd; + fd = openat(datadir_fd, "obumbrata.log", O_WRONLY | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR); + if (fd == -1) + { + return; + } + fp = fdopen(fd, "a"); + if (fp) + { + switch (d) + { + case DEATH_KILLED: + fprintf(fp, "%s was killed by %s.\n", u.name, what); + break; + case DEATH_KILLED_MON: + fprintf(fp, "%s was killed by a nasty %s.\n", u.name, what); + break; + case DEATH_BODY: + fprintf(fp, "%s's heart was stopped by %s.\n", u.name, what); + break; + case DEATH_AGILITY: + fprintf(fp, "%s's nerves were destroyed by %s.\n", u.name, what); + break; + case DEATH_LASH: + fprintf(fp, "%s tasted the lash one time too many.\n", u.name); + break; + case DEATH_RIBBONS: + fprintf(fp, "%s looked good in ribbons.\n", u.name); + break; + } + fprintf(fp, " %s died after %d ticks, with %d XP, on dungeon level %d.\n\n", u.name, game_tick, u.experience, depth); + fflush(fp); + fsync(fd); + fclose(fp); + } + else + { + close(fd); + } +} + /* log.cc */ // vim:cindent:expandtab diff --git a/map.cc b/map.cc index 764fa10..7934185 100644 --- a/map.cc +++ b/map.cc @@ -71,6 +71,7 @@ Chunk::Chunk(Terrain t) terrain[k][m] = t; flags[k][m] = 0; region_number[k][m] = NO_REGION; + decals[k][m] = NO_DECAL; } } } @@ -83,7 +84,6 @@ void initialize_chunks(Level *l, int height, int width, bool dense) int i; int j; drop_all_chunks(l); - l->origin_off = Stationary; l->chunks_high = height; l->chunks_wide = width; new_chunk_grid = new Chunk ** [height]; @@ -285,6 +285,8 @@ void leave_level(void) } iter = iter2; } + lvl.origin_off = Stationary; + drop_all_chunks(&lvl); depth++; } @@ -745,6 +747,7 @@ void serialize_level(FILE *fp, Level const *l) fwrite(tmp_shorts, sizeof tmp_shorts[0], 2, fp); tmp_pair[0] = htonl(l->origin_off.y); tmp_pair[1] = htonl(l->origin_off.x); + fprintf(stderr, "Before saving: origin off is %d %d\n", l->origin_off.y, l->origin_off.x); fwrite(tmp_pair, sizeof tmp_pair[0], 2, fp); tmp = htonl(l->dead_space); fwrite(&tmp, sizeof tmp, 1, fp); @@ -788,8 +791,9 @@ void deserialize_level(FILE *fp, Level *l) l->self.dungeon = ntohs(tmp_shorts[0]); l->self.depth = (int16_t) ntohs(tmp_shorts[1]); wrapped_fread(tmp_pair, sizeof tmp_pair[0], 2, fp); - l->origin_off.y = (int) ntohl(tmp_pair[0]); - l->origin_off.x = (int) ntohl(tmp_pair[1]); + l->origin_off.y = (int32_t) ntohl(tmp_pair[0]); + l->origin_off.x = (int32_t) ntohl(tmp_pair[1]); + fprintf(stderr, "After reloading: origin off is %d %d\n", l->origin_off.y, l->origin_off.x); wrapped_fread(&tmp, sizeof tmp, 1, fp); l->dead_space = Terrain(ntohl(tmp)); wrapped_fread(&tmp, sizeof tmp, 1, fp); diff --git a/map.hh b/map.hh index 532bb03..9be8a98 100644 --- a/map.hh +++ b/map.hh @@ -110,18 +110,21 @@ public: Terrain terrain[CHUNK_EDGE][CHUNK_EDGE]; uint32_t flags[CHUNK_EDGE][CHUNK_EDGE]; uint32_t region_number[CHUNK_EDGE][CHUNK_EDGE]; + Decal_tag decals[CHUNK_EDGE][CHUNK_EDGE]; Terrain terrain_at(Coord c) const { return terrain[c.y][c.x]; } uint32_t flags_at(Coord c) const { return flags[c.y][c.x]; } uint32_t region_at(Coord c) const { return region_number[c.y][c.x]; } - uint32_t obj_at(Coord c) const { return objs[c.y][c.x]; } - uint32_t mon_at(Coord c) const { return mons[c.y][c.x]; } + Obj_handle obj_at(Coord c) const { return objs[c.y][c.x]; } + Mon_handle mon_at(Coord c) const { return mons[c.y][c.x]; } + Decal_tag decal_at(Coord c) const { return decals[c.y][c.x]; } void set_terrain_at(Coord c, Terrain t) { terrain[c.y][c.x] = t; } void overwrite_flags_at(Coord c, uint32_t f) { flags[c.y][c.x] = f; } void set_flags_at(Coord c, uint32_t f) { flags[c.y][c.x] |= f; } void clear_flags_at(Coord c, uint32_t f) { flags[c.y][c.x] &= ~f; } void set_region_at(Coord c, uint32_t r) { region_number[c.y][c.x] = r; } - void set_obj_at(Coord c, int o) { objs[c.y][c.x] = o; } - void set_mon_at(Coord c, int m) { mons[c.y][c.x] = m; } + void set_obj_at(Coord c, Obj_handle o) { objs[c.y][c.x] = o; } + void set_mon_at(Coord c, Mon_handle m) { mons[c.y][c.x] = m; } + void set_decal_at(Coord c, Decal_tag d) { decals[c.y][c.x] = d; } Chunk(Terrain t = WALL); }; @@ -173,82 +176,151 @@ public: std::deque stairs; Terrain terrain_at(Coord c) const { - Coord rc = c + origin_off; - Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; - Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; - return ch ? ch->terrain_at(c2) : dead_space; + if (in_bounds(c)) + { + Coord rc = c + origin_off; + Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; + Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; + return ch ? ch->terrain_at(c2) : dead_space; + } + else + { + return dead_space; + } } void set_terrain_at(Coord c, Terrain t) { - /* Algorithms that want to potentially stretch the level are - * responsible for telling the level to stretch. */ - Coord rc = c + origin_off; - Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; - Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; - if (ch) + if (in_bounds(c)) { - ch->set_terrain_at(c2, t); + Coord rc = c + origin_off; + Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; + Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; + if (ch) + { + ch->set_terrain_at(c2, t); + } } } uint32_t flags_at(Coord c) const { - Coord rc = c + origin_off; - Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; - Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; - return ch ? ch->flags_at(c2) : 0; + if (in_bounds(c)) + { + Coord rc = c + origin_off; + Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; + Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; + return ch ? ch->flags_at(c2) : 0u; + } + else + { + return 0u; + } } void set_flags_at(Coord c, uint32_t to_set) { - Coord rc = c + origin_off; - Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; - Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; - if (ch) + if (in_bounds(c)) { - ch->set_flags_at(c2, to_set); + Coord rc = c + origin_off; + Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; + Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; + if (ch) + { + ch->set_flags_at(c2, to_set); + } } } void clear_flags_at(Coord c, uint32_t to_clear) { - Coord rc = c + origin_off; - Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; - Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; - if (ch) + if (in_bounds(c)) { - ch->clear_flags_at(c2, to_clear); + Coord rc = c + origin_off; + Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; + Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; + if (ch) + { + ch->clear_flags_at(c2, to_clear); + } } } Mon_handle mon_at(Coord c) const { - Coord rc = c + origin_off; - Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; - Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; - return ch ? ch->mon_at(c2) : NO_MON; + if (in_bounds(c)) + { + Coord rc = c + origin_off; + Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; + Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; + return ch ? ch->mon_at(c2) : NO_MON; + } + else + { + return NO_MON; + } } void set_mon_at(Coord c, Mon_handle mon) { - Coord rc = c + origin_off; - Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; - Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; - if (ch) + if (in_bounds(c)) { - ch->set_mon_at(c2, mon); + Coord rc = c + origin_off; + Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; + Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; + if (ch) + { + ch->set_mon_at(c2, mon); + } } } Obj_handle obj_at(Coord c) const { - Coord rc = c + origin_off; - Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; - Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; - return ch ? ch->obj_at(c2) : NO_OBJ; + if (in_bounds(c)) + { + Coord rc = c + origin_off; + Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; + Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; + return ch ? ch->obj_at(c2) : NO_OBJ; + } + else + { + return NO_OBJ; + } } void set_obj_at(Coord c, Obj_handle obj) { - Coord rc = c + origin_off; - Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; - Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; - if (ch) + if (in_bounds(c)) + { + Coord rc = c + origin_off; + Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; + Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; + if (ch) + { + ch->set_obj_at(c2, obj); + } + } + } + Decal_tag decal_at(Coord c) const + { + if (in_bounds(c)) + { + + Coord rc = c + origin_off; + Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; + Chunk const *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; + return ch ? ch->decal_at(c2) : NO_DECAL; + } + else + { + return NO_DECAL; + } + } + void set_decal_at(Coord c, Decal_tag t) + { + if (in_bounds(c)) { - ch->set_obj_at(c2, obj); + Coord rc = c + origin_off; + Coord c2 = { rc.y & CHUNK_MASK, rc.x & CHUNK_MASK }; + Chunk *ch = chunks[rc.y >> CHUNK_SHIFT][rc.x >> CHUNK_SHIFT]; + if (ch) + { + ch->set_decal_at(c2, t); + } } } Coord random_point(int margin) const; @@ -256,7 +328,7 @@ public: { c += origin_off; return !((c.y < 0) || (c.x < 0) || (c.y >= (chunks_high << CHUNK_SHIFT)) || - (c.x >= (chunks_wide << CHUNK_SHIFT))); + (c.x >= (chunks_wide << CHUNK_SHIFT))); } int min_y() const { diff --git a/monsters.cc b/monsters.cc index 25847ad..d643081 100644 --- a/monsters.cc +++ b/monsters.cc @@ -185,58 +185,58 @@ void death_drop(Mon_handle mon) case PM_GOBLIN: if (!zero_die(4)) { - create_obj(PO_DAGGER, 1, 0, c); + create_obj_near(PO_DAGGER, 1, c); } break; case PM_THUG: case PM_GOON: if (!zero_die(4)) { - create_obj(PO_MACE, 1, 0, c); + create_obj_near(PO_MACE, 1, c); } else if (!zero_die(3)) { - create_obj(PO_LEATHER_ARMOUR, 1, 0, c); + create_obj_near(PO_LEATHER_ARMOUR, 1, c); } break; case PM_HUNTER: if (!zero_die(6)) { - create_obj(PO_BOW, 1, 0, c); + create_obj_near(PO_BOW, 1, c); } break; case PM_DUELLIST: if (!zero_die(6)) { - create_obj(PO_LONG_SWORD, 1, 0, c); + create_obj_near(PO_LONG_SWORD, 1, c); } break; case PM_WIZARD: if (!zero_die(4)) { - create_obj_class(POCLASS_SCROLL, 1, 0, c); + create_obj_class_near(POCLASS_SCROLL, 1, false, c); } else if (!zero_die(3)) { - create_obj_class(POCLASS_POTION, 1, 0, c); + create_obj_class_near(POCLASS_POTION, 1, false, c); } break; case PM_WARLORD: if (!zero_die(3)) { - create_obj(PO_RUNESWORD, 1, 0, c); + create_obj_near(PO_RUNESWORD, 1, c); } break; case PM_DEMON: if (!zero_die(100)) { - create_obj(PO_DEVIL_SPLEEN, 1, 0, c); + create_obj_near(PO_DEVIL_SPLEEN, 1, c); } break; case PM_DEFILER: if (!zero_die(50)) { - create_obj(PO_DEVIL_SPLEEN, 1, 0, c); + create_obj_near(PO_DEVIL_SPLEEN, 1, c); } break; default: @@ -313,14 +313,38 @@ void unplace_mon(Mon_handle mon) monsters[mon].flags &= ~MF_USED; } +void apply_liquid(int pm, Coord c) +{ + if (pmon_bleeds(pm)) + { + lvl.set_decal_at(c, Decal_blood); + } + else if (pmon_suppurates(pm)) + { + lvl.set_decal_at(c, Decal_pus); + } +} + /*! \brief Handle the death of a monster * * \todo Support special effects on monster death */ -void kill_mon(Mon_handle mon, bool by_you) +void kill_mon(Mon_handle mon, bool by_you, bool explode_corpse) { + Mon *mptr = mon_snapv(mon); + int pm = mptr->pm_ref; + apply_liquid(pm, mptr->pos); + if (pmon_explodes(pm) || (pmon_leaves_corpse(pm) && explode_corpse)) + { + // TODO shower the surroundings with the monster's liquid + } + else if (pmon_leaves_corpse(pm)) + { + create_corpse(pm, mptr->pos); + } death_drop(mon); // phat lewt! - monsters[mon].hpcur = -1; // Set HP to -1 so nothing will think it's alive + mptr->flags &= ~MF_ALIVE; + monsters[mon].hpcur = -1; unplace_mon(mon); // cleanup if (by_you) { @@ -610,5 +634,33 @@ bool Mon::can_fly(void) const return pmon_can_fly(pm_ref); } -/* monsters.c */ +void asprint_mon_name(char **buf, Mon_handle mon, int article) +{ + Mon const *mptr = mon_snap(mon); + int i; + switch (article) + { + case 0: + i = asprintf(buf, "a%s %s", is_vowel(permons[mptr->pm_ref].name[0]) ? "n" : "", permons[mptr->pm_ref].name); + break; + case 1: + i = asprintf(buf, "the %s", permons[mptr->pm_ref].name); + break; + case 2: + i = asprintf(buf, "A%s %s", is_vowel(permons[mptr->pm_ref].name[0]) ? "n" : "", permons[mptr->pm_ref].name); + break; + case 3: + i = asprintf(buf, "The %s", permons[mptr->pm_ref].name); + break; + default: + i = -1; + } + if (i == -1) + { + // TODO wibble + } + return; +} + +/* monsters.cc */ // vim:cindent diff --git a/monsters.hh b/monsters.hh index a0eb5f9..6554eac 100644 --- a/monsters.hh +++ b/monsters.hh @@ -86,7 +86,7 @@ inline Mon const *mon_snap(Mon_handle mon) void update_mon(Mon_handle mon); void mon_acts(Mon_handle mon); void death_drop(Mon_handle mon); -void print_mon_name(Mon_handle mon, int article); +void asprint_mon_name(char **buf, Mon_handle mon, int article); void summon_demon_near(Coord c); Mon_handle create_mon(int pm_idx, Coord c); int summoning(Coord c, int how_many); @@ -100,7 +100,7 @@ void reloc_mon(Mon_handle mon, Coord c); Pass_fail teleport_mon(Mon_handle mon); /* Randomly relocate monster. */ Pass_fail teleport_mon_to_you(Mon_handle mon); /* Relocate monster to your vicinity. */ void heal_mon(Mon_handle mon, int amount, int cansee); -void kill_mon(Mon_handle mon, bool by_you); +void kill_mon(Mon_handle mon, bool by_you, bool explode_corpse = false); void unplace_mon(Mon_handle mon); /* XXX mon2.c data and funcs */ diff --git a/notify-local-tty.cc b/notify-local-tty.cc index c976ca3..ecb5742 100644 --- a/notify-local-tty.cc +++ b/notify-local-tty.cc @@ -242,6 +242,12 @@ void notify_blocked_water(void) print_msg("The idiot who raised you never taught you to swim.\n"); } +void notify_new_obj_at(Coord c, Obj_handle obj) +{ + newsym(c); + display_update(); +} + void notify_obj_at(Coord c) { print_msg("You see here "); diff --git a/notify.hh b/notify.hh index a90d738..325e670 100644 --- a/notify.hh +++ b/notify.hh @@ -62,6 +62,7 @@ void notify_blocked_water(void); void notify_cant_go(void); // Unsorted notifications +void notify_new_obj_at(Coord c, Obj_handle obj); void notify_obj_at(Coord c); void notify_dress_shredded(void); void notify_mon_healed(Mon_handle mon); diff --git a/objects.cc b/objects.cc index a3c198b..07914e1 100644 --- a/objects.cc +++ b/objects.cc @@ -320,7 +320,7 @@ Obj_handle get_first_free_obj(void) return first_free_obj_handle++; } -Obj_handle create_obj_class(enum poclass_num po_class, int quantity, bool with_you, Coord c) +Obj_handle create_obj_class_near(enum poclass_num po_class, int quantity, bool with_you, Coord c) { int po_idx; int tryct; @@ -347,7 +347,7 @@ Obj_handle create_obj_class(enum poclass_num po_class, int quantity, bool with_y } break; } - return create_obj(po_idx, quantity, with_you, c); + return (with_you ? create_obj(po_idx, quantity, with_you, c) : create_obj_near(po_idx, quantity, c)); } int get_random_pobj(void) @@ -372,6 +372,45 @@ int get_random_pobj(void) return po_idx; } +Obj_handle create_corpse(int pm_idx, Coord c) +{ + Obj_handle obj = create_obj_near(PO_CORPSE, 1, c); + if (obj != NO_OBJ) + { + Obj *o = obj_snapv(obj); + o->meta[0] = pm_idx; + o->meta[1] = 0; // reserved + o->meta[2] = 0; // reserved + o->meta[3] = 0; // reserved + } + return obj; +} + +Obj_handle create_obj_near(int po_idx, int quantity, Coord c) +{ + Offset delta; + Coord nc; + int panic_count = 200; + while ((lvl.obj_at(c) != NO_OBJ) && (panic_count > 0)) + { + do + { + delta = random_step(); + nc = c + delta; + } + while (terrain_blocks_beings(lvl.terrain_at(nc))); + c = nc; + --panic_count; + } + if (panic_count < 1) + { + // TODO debugging report for failure + return NO_OBJ; + } + Obj_handle obj = create_obj(po_idx, quantity, false, c); + return obj; +} + Obj_handle create_obj(int po_idx, int quantity, bool with_you, Coord c) { Obj_handle obj = get_first_free_obj(); @@ -399,9 +438,6 @@ Obj_handle create_obj(int po_idx, int quantity, bool with_you, Coord c) { case POCLASS_WEAPON: case POCLASS_ARMOUR: - /* Set durability of weapons and armour to a suitable value. - * 100 has been chosen for the moment, but this may need - * tuning. */ o.durability = OBJ_MAX_DUR; break; default: @@ -411,36 +447,48 @@ Obj_handle create_obj(int po_idx, int quantity, bool with_you, Coord c) if (!(o.flags & OF_WITH_YOU)) { lvl.set_obj_at(c, obj); + notify_new_obj_at(c, obj); } return obj; } -void sprint_obj_name(char *buf, Obj_handle obj, int len) +void asprint_obj_name(char **buf, Obj_handle obj) { Obj *optr; Permobj *poptr; + int i; optr = obj_snapv(obj); poptr = permobjs + optr->po_ref; if (optr->quan > 1) { - snprintf(buf, len, "%d %s", optr->quan, poptr->plural); + i = asprintf(buf, "%d %s", optr->quan, poptr->plural); } else if (po_is_stackable(optr->po_ref)) { - snprintf(buf, len, "1 %s", poptr->name); + i = asprintf(buf, "1 %s", poptr->name); } else if ((poptr->poclass == POCLASS_WEAPON) || (poptr->poclass == POCLASS_ARMOUR)) { - snprintf(buf, len, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR); + i = asprintf(buf, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR); + } + else if (optr->po_ref == PO_CORPSE) + { + Permon *pmptr = permons + optr->meta[0]; + i = asprintf(buf, "a%s %s corpse", is_vowel(pmptr->name[0]) ? "n" : "", pmptr->name); } else { - snprintf(buf, len, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name); + i = asprintf(buf, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name); + } + if (i == -1) + { + // TODO wibble } + return; } -void fprint_obj_name(FILE *fp, Obj_handle obj) +void sprint_obj_name(char *buf, Obj_handle obj, int len) { Obj *optr; Permobj *poptr; @@ -448,23 +496,36 @@ void fprint_obj_name(FILE *fp, Obj_handle obj) poptr = permobjs + optr->po_ref; if (optr->quan > 1) { - fprintf(fp, "%d %s", optr->quan, poptr->plural); + snprintf(buf, len, "%d %s", optr->quan, poptr->plural); } else if (po_is_stackable(optr->po_ref)) { - fprintf(fp, "1 %s", poptr->name); + snprintf(buf, len, "1 %s", poptr->name); } else if ((poptr->poclass == POCLASS_WEAPON) || (poptr->poclass == POCLASS_ARMOUR)) { - fprintf(fp, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR); + snprintf(buf, len, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR); + } + else if (optr->po_ref == PO_CORPSE) + { + Permon *pmptr = permons + optr->meta[0]; + snprintf(buf, len, "a%s %s corpse", is_vowel(pmptr->name[0]) ? "n" : "", pmptr->name); } else { - fprintf(fp, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name); + snprintf(buf, len, "a%s %s", is_vowel(poptr->name[0]) ? "n" : "", poptr->name); } } +void fprint_obj_name(FILE *fp, Obj_handle obj) +{ + char *s; + asprint_obj_name(&s, obj); + fputs(s, fp); + free(s); +} + Action_cost drop_obj(int inv_idx) { Obj *optr; diff --git a/objects.hh b/objects.hh index 90db33e..7e0396d 100644 --- a/objects.hh +++ b/objects.hh @@ -50,17 +50,21 @@ public: uint32_t flags; int quan; Coord pos; + uint32_t meta[4]; // Things like what pmon a corpse is int durability; /* Weapons and armour degrade with use. */ }; extern std::map objects; /* XXX objects.cc data and funcs */ +void asprint_obj_name(char **s, Obj_handle obj); void sprint_obj_name(char *s, Obj_handle obj, int len); void fprint_obj_name(FILE *fp, Obj_handle obj); void describe_object(Obj_handle obj); +Obj_handle create_corpse(int pm_idx, Coord c); +Obj_handle create_obj_near(int po_idx, int quantity, Coord c); Obj_handle create_obj(int po_idx, int quantity, bool with_you, Coord c); bool consume_obj(Obj_handle obj); -Obj_handle create_obj_class(enum poclass_num pocl, int quantity, bool with_you, Coord c); +Obj_handle create_obj_class_near(enum poclass_num pocl, int quantity, bool with_you, Coord c); void damage_obj(Obj_handle obj); int evasion_penalty(Obj_handle obj); diff --git a/permon.hh b/permon.hh index 0fc6fcb..0e31244 100644 --- a/permon.hh +++ b/permon.hh @@ -57,7 +57,18 @@ #define PMF_QUADRUPED 0x10000000 #define PMF_SKITTERISH 0x20000000 #define PMF_HUGE 0x40000000 -#define PMF_SMALL 0x80000000 +#define PMF_SMALL 0x80000000u + +#define PMF_MADE_OF_MEAT 0x00000001u +#define PMF_MADE_OF_GOO 0x00000002u +#define PMF_MADE_OF_BONE 0x00000004u +#define PMF_MADE_OF_METAL 0x00000008u +#define PMF_MADE_OF_ICE 0x00000010u +#define PMF_MADE_OF_FIRE 0x00000020u +#define PMF_CONTAINS_BLOOD 0x00010000u +#define PMF_CONTAINS_PUS 0x00020000u +#define PMF_BURSTS_ON_DEATH 0x40000000u +#define PMF_LEAVES_CORPSE 0x80000000u #define PERMON_FLAG_FIELDS 2 /*! \brief Describes the baseline stats of a specific kind of monster */ @@ -93,33 +104,37 @@ enum AI_mode { AI_charger, AI_archer, AI_dodger, AI_drunk, AI_seeker, AI_chaser }; -/* XXX pmon2.c data and funcs */ -extern bool pmon_is_archer(int pm); -extern bool pmon_is_magician(int pm); -extern bool pmon_is_smart(int pm); -extern bool pmon_is_stupid(int pm); - -extern bool pmon_resists_cold(int pm); -extern bool pmon_resists_fire(int pm); -extern bool pmon_resists_poison(int pm); -extern bool pmon_resists_necro(int pm); -extern bool pmon_resists_elec(int pm); -extern bool pmon_resists_drowning(int pm); -extern bool pmon_resists_knockback(int pm); - -extern bool pmon_can_fly(int pm); - -extern bool pmon_is_ethereal(int pm); -extern bool pmon_is_undead(int pm); -extern bool pmon_is_demonic(int pm); - -extern bool pmon_has_hands(int pm); -extern bool pmon_is_humanoid(int pm); -extern bool pmon_is_quadruped(int pm); -extern bool pmon_is_skitterish(int pm); -extern bool pmon_is_amorphous(int pm); -extern bool pmon_is_huge(int pm); -extern bool pmon_is_small(int pm); +bool pmon_is_archer(int pm); +bool pmon_is_magician(int pm); +bool pmon_is_smart(int pm); +bool pmon_is_stupid(int pm); + +bool pmon_resists_cold(int pm); +bool pmon_resists_fire(int pm); +bool pmon_resists_poison(int pm); +bool pmon_resists_necro(int pm); +bool pmon_resists_elec(int pm); +bool pmon_resists_drowning(int pm); +bool pmon_resists_knockback(int pm); + +bool pmon_can_fly(int pm); + +bool pmon_is_ethereal(int pm); +bool pmon_is_undead(int pm); +bool pmon_is_demonic(int pm); + +bool pmon_has_hands(int pm); +bool pmon_is_humanoid(int pm); +bool pmon_is_quadruped(int pm); +bool pmon_is_skitterish(int pm); +bool pmon_is_amorphous(int pm); +bool pmon_is_huge(int pm); +bool pmon_is_small(int pm); + +inline bool pmon_bleeds(int pm) { return permons[pm].flags[1] & PMF_CONTAINS_BLOOD; } +inline bool pmon_suppurates(int pm) { return permons[pm].flags[1] & PMF_CONTAINS_PUS; } +inline bool pmon_explodes(int pm) { return permons[pm].flags[1] & PMF_BURSTS_ON_DEATH; } +inline bool pmon_leaves_corpse(int pm) { return permons[pm].flags[1] & PMF_LEAVES_CORPSE; } #endif diff --git a/player.hh b/player.hh index b7fb924..deb0864 100644 --- a/player.hh +++ b/player.hh @@ -33,9 +33,12 @@ /*! \brief Internal representation of the player character */ +#define INVENTORY_SIZE 19 class Player { public: char name[17]; //!< Allows 16 actual characters, plus 0 terminator + Mon_handle mh; //!< Handle for monster representing the player. + Mon *mptr; //!< Pointer for monster representing the player. Coord pos; //!< Position within current dungeon level. int body; //!< Combined stamina and strength stat. int bdam; //!< Current level of temporary Body drain @@ -53,7 +56,7 @@ public: int speed; //!< Controls how often you act. uint32_t resistances[DT_COUNT]; //!< Resistance masks per damage type int level; //!< Current experience level. - Obj_handle inventory[19]; //!< currently carried items. + Obj_handle inventory[INVENTORY_SIZE]; //!< currently carried items. Obj_handle weapon; //!< currently equipped weapon. Obj_handle armour; //!< currently equipped armour. Obj_handle ring; //!< currently equipped ring. diff --git a/pmon_comp b/pmon_comp index e2405e1..207f50c 100755 --- a/pmon_comp +++ b/pmon_comp @@ -38,6 +38,16 @@ our %flag_indices = 'SKITTERISH' => 0, 'HUGE' => 0, 'SMALL' => 0, + 'MADE_OF_MEAT' => 1, + 'MADE_OF_GOO' => 1, + 'MADE_OF_BONE' => 1, + 'MADE_OF_METAL' => 1, + 'MADE_OF_ICE' => 1, + 'MADE_OF_FIRE' => 1, + 'CONTAINS_BLOOD' => 1, + 'CONTAINS_PUS' => 1, + 'BURSTS_ON_DEATH' => 1, + 'LEAVES_CORPSE' => 1, ); -- 2.11.0