From 13898f809e251ccd415144b955a315eb75e0b5ed Mon Sep 17 00:00:00 2001 From: Martin Read Date: Wed, 12 Mar 2014 16:00:50 +0000 Subject: [PATCH] Big ball-of-mud commit Whole bunch of things in here, including: * Files which don't need to refer to specific permons or permobjs by ID should no longer need to depend on the ID-list headers, as the NUM_OF_xxx constants for these arrays have been converted from preprocessor macros to objects of type "const int". * A modicum of version-checking and referential integrity checking has been added to load_game(), which has also been converted to use exceptions to indicate failure. * A bunch of things now use lookup tables (either indexed or linear search) instead of big clunky switches. * Some item-attribute work is still in progress, which should allow a bunch of switches on permobj IDs to be replaced with tests for object flags. (Also, in principle the game should now support any piece of equipment conveying any resistance.) * A few other lingering bugs from Victrix Abyssi have been caught and fixed. * The nutritional value of different pieces of food is now defined in the permobj database rather than being a single hard-coded constant in eat_food(). * There is now a choice of three character classes: Princess, Demon Hunter, and Thanatophile. --- MANIFEST | 3 + Makefile | 10 ++- combat.cc | 10 ++- combo.cc | 191 ++++++++++++++++++++++++++++++++++++++++++ core.hh | 51 +++++++++++- deeds.cc | 2 +- default.permobjs | 66 ++++++++++++--- display-nc.cc | 145 ++++++++++++++++++++++++++------ log.cc | 236 +++++++++++++++++++++++++++++++++++++++------------- main.cc | 9 +- map.cc | 52 ++++++++---- map.hh | 7 +- mon1.cc | 43 +++++++--- monsters.hh | 1 + notify-local-tty.cc | 52 +++++++++--- notify.hh | 10 ++- obj1.cc | 22 +++-- obumbrata.hh | 12 +-- permobj.hh | 31 +++++-- permon.hh | 4 +- player.hh | 57 ++++++++++++- pmon2.cc | 13 +++ pmon_comp | 7 +- pobj_comp | 42 ++++++---- role.cc | 76 +++++++++++++++++ skills.cc | 88 ++++++++++++++++++++ u.cc | 213 ++++++++++++++++------------------------------- 27 files changed, 1121 insertions(+), 332 deletions(-) create mode 100644 combo.cc create mode 100644 role.cc create mode 100644 skills.cc diff --git a/MANIFEST b/MANIFEST index c73258c..a532a90 100644 --- a/MANIFEST +++ b/MANIFEST @@ -7,6 +7,7 @@ man/obumbrata.6 cave.cc combat.cc combat.hh +combo.cc coord.cc coord.hh core.hh @@ -40,7 +41,9 @@ pmon_comp pobj_comp rng.cc rng.hh +role.cc shrine.cc +skills.cc sorcery.cc sorcery.hh u.cc diff --git a/Makefile b/Makefile index 7fa3a1c..ae3feac 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ vpath %.o . GENERATED_OBJS:=permobj.o permons.o GENERATED_SOURCE:=permobj.cc pobj_id.hh permons.cc pmon_id.hh GENERATED_MAKES:=dirs.mk features.mk -HANDWRITTEN_OBJS:=cave.o combat.o coord.o deeds.o display-nc.o fov.o log.o main.o map.o mon1.o mon2.o mon3.o notify-local-tty.o obj1.o obj2.o pmon2.o rng.o shrine.o sorcery.o u.o util.o +HANDWRITTEN_OBJS:=cave.o combat.o combo.o coord.o deeds.o display-nc.o fov.o log.o main.o map.o mon1.o mon2.o mon3.o notify-local-tty.o obj1.o obj2.o pmon2.o rng.o role.o shrine.o sorcery.o u.o util.o OBJS:=$(GENERATED_OBJS) $(HANDWRITTEN_OBJS) GAME:=obumbrata MAJVERS:=1 @@ -44,7 +44,7 @@ debianize-archive: archive my-debworkflow: my-debclean debianize-archive mkdir archive-test - cp $(ARCHIVENAME).orig.tar.gz archive-test + mv $(ARCHIVENAME).orig.tar.gz archive-test (cd archive-test && tar xzf $(ARCHIVENAME).orig.tar.gz) cp -R debian archive-test/$(ARCHIVEDIR)/debian (cd archive-test/$(ARCHIVEDIR) && debuild -uc -us) @@ -97,6 +97,8 @@ cave.o: $(srcdir)/cave.cc $(srcdir)/$(GAME).hh $(srcdir)/map.hh $(srcdir)/mapgen combat.o: $(srcdir)/combat.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/monsters.hh $(srcdir)/objects.hh $(srcdir)/notify.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh +combo.o: $(srcdir)/combo.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh + coord.o: $(srcdir)/coord.cc $(srcdir)/coord.hh deeds.o: $(srcdir)/deeds.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh @@ -127,6 +129,10 @@ obj1.o: $(srcdir)/obj1.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/obj obj2.o: $(srcdir)/obj2.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh +rng.o: $(srcdir)/rng.cc $(srcdir)/rng.hh + +role.o: $(srcdir)/role.cc $(srcdir)/combat.hh $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/monsters.hh $(srcdir)/objects.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh + shrine.o: $(srcdir)/shrine.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh $(srcdir)/map.hh $(srcdir)/mapgen.hh sorcery.o: $(srcdir)/sorcery.cc $(srcdir)/$(GAME).hh $(srcdir)/notify.hh $(srcdir)/sorcery.hh $(srcdir)/objects.hh $(srcdir)/monsters.hh ./pobj_id.hh ./pmon_id.hh $(srcdir)/player.hh diff --git a/combat.cc b/combat.cc index fe9664c..010b79a 100644 --- a/combat.cc +++ b/combat.cc @@ -116,7 +116,10 @@ void resolve_player_melee(Mon_handle mon, int damage) } if (u.weapon != NO_OBJ) { - damage_obj(u.weapon); + if (permobjs[objects[u.weapon].po_ref].flags[0] & POF_DAMAGEABLE) + { + damage_obj(u.weapon); + } } } @@ -256,7 +259,10 @@ int mhitu(Mon_handle mon, Damtyp dtype) if ((u.armour != NO_OBJ) && (tohit > agility_modifier())) { /* Monster hit your armour. */ - damage_obj(u.armour); + if (permobjs[objects[u.armour].po_ref].flags[0] & POF_DAMAGEABLE) + { + damage_obj(u.armour); + } notify_mon_hit_armour(mon); } else diff --git a/combo.cc b/combo.cc new file mode 100644 index 0000000..6e3467c --- /dev/null +++ b/combo.cc @@ -0,0 +1,191 @@ +/*! \file combo.cc + * \brief Combo detection for Obumbrata et Velata + */ + +/* + * Copyright © 2014 Martin Read + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * 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. + */ + +#include "obumbrata.hh" +#include "combat.hh" +#include "objects.hh" +#include +#include +#include +#include + +/* Design note: Completing a combo discards the moves that comprised the + * combo from the list, and pushes the combo's special action itself if the + * combo is marked as Combo_valid. */ + +static bool rewrite_stand_still_1(Action *revisable_action) +{ + return false; +} + +static bool rewrite_walk_1(Action *revisable_action) +{ + Mon_handle mon; + Offset o = { (int32_t) revisable_action->details[0], (int32_t) revisable_action->details[1] }; + Coord c = u.pos + o; + if (!lvl.in_bounds(c)) + { + debug_move_oob(c); + revisable_action->cmd = REJECTED_ACTION; + return true; + } + else if (lvl.mon_at(c) != NO_MON) + { + mon = lvl.mon_at(c); + revisable_action->cmd = ATTACK; + return true; + } + return false; +} + +static bool rewrite_attack_1(Action *revisable_action) +{ + Offset o = { (int32_t) revisable_action->details[0], (int32_t) revisable_action->details[1] }; + Coord c = u.pos + o; + if (!lvl.in_bounds(c)) + { + debug_move_oob(c); // TODO notify_attack_oob() + revisable_action->cmd = REJECTED_ACTION; + return true; + } + return false; +} + +Action_rewriter_phase1_func action_rewrite_phase1[NUM_COMMANDS] = +{ + rewrite_walk_1, + rewrite_stand_still_1, + nullptr, // rewrite_ascend_1 + nullptr, // rewrite_descend_1 + rewrite_attack_1, + nullptr, // rewrite_get_1 + nullptr, // rewrite_drop_1 + nullptr, // rewrite_wield_1 + nullptr, // rewrite_armour_on_1 + nullptr, // rewrite_armour_off_1 + nullptr, // rewrite ring_on_1 + nullptr, // rewrite_ring_off_1 + nullptr, // rewrite_quaff_1 + nullptr, // rewrite_read_1 + nullptr, // rewrite_throw_1 + nullptr, // rewrite_eat_1 + nullptr, // rewrite_emanate_1 + nullptr, // rewrite_zap_1 + nullptr, // rewrite_ringmagic_1 + nullptr, // rewrite_use_skill_1 + nullptr, // rewrite_allocate_skill_1 + nullptr, // rewrite_save_1 + nullptr, // rewrite_quit_1 + nullptr, // rewrite_wizard_levup_1 + nullptr, // rewrite_wizard_descend_1 + nullptr, // rewrite_powatk_1 +}; + +Combo_phase action_default_combophase[NUM_COMMANDS] = +{ + Combo_valid, // walk + Combo_valid, // stand still + Combo_invalid, // go up + Combo_invalid, // go down + Combo_finisher, // attack + Combo_invalid, // get + Combo_invalid, // drop + Combo_invalid, // wield + Combo_invalid, // wear + Combo_invalid, // take off + Combo_invalid, // put on + Combo_invalid, // remove + Combo_invalid, // quaff + Combo_invalid, // read + Combo_invalid, // throw + Combo_invalid, // eat + Combo_invalid, // emanate + Combo_invalid, // zap + Combo_invalid, // magic (ring) + Combo_invalid, // use skill + Combo_invalid, // allocate skill poiont + Combo_invalid, // save + Combo_invalid, // quit + Combo_invalid, // wizard mode levelup + Combo_invalid, // wizard mode descend + Combo_invalid, // combo: power attack +}; + +static bool powatk_avail(Player const *player) +{ + if (player->level >= 2) + { + if (player->weapon != NO_OBJ) + { + Obj const *optr = obj_snap(player->weapon); + if (permobjs[optr->po_ref].flags[0] & POF_RANGED_WEAPON) + { + return false; + } + } + return true; + } + return false; +} + +static Combo_state powatk_detector(std::deque* action_list) +{ + if ((*action_list)[0].cmd == STAND_STILL) + { + if ((*action_list).size() > 1) + { + if ((*action_list)[1].cmd == ATTACK) + { + (*action_list)[0] = (*action_list)[1]; + (*action_list)[0].cmd = COMBO_POWATK; + action_list->resize(1); + return Combo_state_complete; + } + return Combo_state_none; + } + else + { + return Combo_state_partial; + } + } + else + { + return Combo_state_none; + } +} + +Combo_entry combo_entries[] = +{ + /* Everyone gets power attack at level 2. */ + { "power attack", STAND_STILL, powatk_avail, powatk_detector }, + { "empty fencepost entry", REJECTED_ACTION, nullptr, nullptr } +}; + +/* combo.cc */ +// vim:cindent:expandtab diff --git a/core.hh b/core.hh index 46bd0c2..9aa0701 100644 --- a/core.hh +++ b/core.hh @@ -38,6 +38,7 @@ #include #include #include +#include #ifndef COORD_HH #include "coord.hh" @@ -114,17 +115,33 @@ enum Game_cmd { ATTACK, GET_ITEM, DROP_ITEM, WIELD_WEAPON, WEAR_ARMOUR, TAKE_OFF_ARMOUR, PUT_ON_RING, REMOVE_RING, - QUAFF_POTION, READ_SCROLL, THROW_FLASK, EAT_FOOD, + QUAFF_POTION, READ_SCROLL, THROW_ITEM, EAT_FOOD, EMANATE_ARMOUR, ZAP_WEAPON, MAGIC_RING, USE_ACTIVE_SKILL, ALLOCATE_SKILL_POINT, SAVE_GAME, QUIT, WIZARD_LEVELUP, WIZARD_DESCEND, /* combos begin here */ - CMD_POWER_ATTACK + COMBO_POWATK }; -#define LAST_COMMAND (CMD_POWER_ATTACK) +#define LAST_COMMAND (COMBO_POWATK) #define NUM_COMMANDS (1 + LAST_COMMAND) +/*! \brief Is this action a valid combo step? */ +enum Combo_phase +{ + Combo_invalid, // No, it isn't. + Combo_valid, // Yes, it is. + Combo_finisher // Only at the end of a combo. +}; + +/*! \brief Did we detect a combo? */ +enum Combo_state +{ + Combo_state_none, // We did not detect the combo + Combo_state_partial, // We detected a leading subset of the combo + Combo_state_complete // We detected the whole combo +}; + /*! \brief Identification code for ways to die * * Sadly, there are not 52 kinds of way to die. @@ -160,7 +177,6 @@ struct Action uint32_t details[8]; }; - class Level; class Level_key; class Permobj; @@ -195,6 +211,33 @@ struct Inferno_detail int dice_bonus; }; +enum Role_id { + Role_none = -1, + Role_princess = 0, + Role_demon_hunter, + Role_thanatophile +}; +#define LAST_ROLE (Role_thanatophile) +#define NUM_ROLES (1 + LAST_ROLE) + +#define OBUMBRATA_MAGIC_NUMBER 0x0b756d62 + +extern void game_cleanup(void); + +struct Obumb_sysexcep +{ + int err; // errno value + Obumb_sysexcep() = delete; + Obumb_sysexcep(int e) : err(e) { } +}; + +struct Obumb_dataexcep +{ + char const *str; + Obumb_dataexcep() = delete; + Obumb_dataexcep(char const *s) : str(s) { } +}; + #endif /* core.hh */ diff --git a/deeds.cc b/deeds.cc index 95a166a..309ad0a 100644 --- a/deeds.cc +++ b/deeds.cc @@ -283,7 +283,7 @@ static Action_cost deed_save(Action const *act) return Cost_none; } -deed_func deed_funcs[NUM_COMMANDS] = +Deed_func deed_funcs[NUM_COMMANDS] = { deed_walk, deed_stand_still, diff --git a/default.permobjs b/default.permobjs index 8df90c8..5878364 100644 --- a/default.permobjs +++ b/default.permobjs @@ -37,7 +37,7 @@ MELEE_WEAPON WEAPON long sword PLURAL long swords -DESC A steel sword of simple but sturdy construction; the blade is three feet long. +DESC The evolution of the cruciform sword in response to ever-heavier plate armour, featuring an unsharpened ricasso to allow its use 'at the half-sword'. RARITY 30 ASCII ')' UTF8 ")" @@ -48,18 +48,20 @@ DEPTH 4 DAMAGEABLE MELEE_WEAPON -WEAPON mace -PLURAL maces -DESC A flanged lump of iron on an iron haft. -RARITY 30 +WEAPON windsword +PLURAL windswords +DESC A magic sword imbued with the power of the rushing winds, enabling the wielder to pass safely over such hazards as water, lava, and gaping chasms. +RARITY 80 ASCII ')' UTF8 ")" -COLOUR iron -POWER 7 +COLOUR white +POWER 13 POWER2 0 -DEPTH 2 +DEPTH 10 DAMAGEABLE MELEE_WEAPON +NOTIFY_EQUIP +FLIGHT WEAPON runesword PLURAL runeswords @@ -74,6 +76,32 @@ DEPTH 12 DAMAGEABLE MELEE_WEAPON +WEAPON mace +PLURAL maces +DESC A flanged lump of iron on an iron haft. +RARITY 30 +ASCII ')' +UTF8 ")" +COLOUR iron +POWER 7 +POWER2 0 +DEPTH 2 +DAMAGEABLE +MELEE_WEAPON + +WEAPON spear +PLURAL spears +DESC A blade on a stick. +RARITY 50 +ASCII ')' +UTF8 ")" +COLOUR brown +POWER 8 +POWER2 0 +DEPTH 3 +DAMAGEABLE +MELEE_WEAPON + WEAPON hellglaive PLURAL hellglaives DESC A black-hafted polearm with a long, wickedly serrated blade. It thrums with infernal power. @@ -361,6 +389,7 @@ POWER 12 POWER2 10 DEPTH 21 DAMAGEABLE +RES_FIRE ARMOUR meteoric plate armour PLURAL suits of meteoric plate armour @@ -373,6 +402,7 @@ POWER 18 POWER2 40 DEPTH 27 DAMAGEABLE +RES_FIRE ARMOUR sacred chainmail PLURAL suits of sacred chainmail @@ -408,7 +438,7 @@ UTF8 "[" COLOUR green POWER 3 POWER2 10 -DEPTH 1 +DEPTH 3 DRESS DAMAGEABLE BREAK_REACT @@ -440,6 +470,7 @@ DEPTH 30 NOTIFY_EQUIP DAMAGEABLE BREAK_REACT +RES_POISON ARMOUR lich's robe PLURAL lich's robes @@ -452,6 +483,7 @@ POWER 15 POWER2 0 DEPTH 30 NOTIFY_EQUIP +RES_NECRO ARMOUR infernite armour PLURAL suits of infernite armour @@ -464,6 +496,7 @@ POWER 25 POWER2 20 DEPTH 30 NOTIFY_EQUIP +RES_FIRE RING regeneration ring PLURAL regeneration rings @@ -486,6 +519,8 @@ COLOUR l_grey POWER 0 POWER2 0 DEPTH 1 +RES_FIRE +DMG_FIRE RING vampire ring PLURAL vampire rings @@ -497,6 +532,8 @@ COLOUR l_grey POWER 0 POWER2 0 DEPTH 12 +RES_NECRO +DMG_NECRO RING frost ring PLURAL frost rings @@ -508,6 +545,9 @@ COLOUR l_grey POWER 0 POWER2 0 DEPTH 1 +RES_COLD +DMG_COLD +PASS_WATER RING teleport ring PLURAL teleport rings @@ -530,6 +570,7 @@ COLOUR l_grey POWER 0 POWER2 0 DEPTH 15 +RES_POISON RING protection ring PLURAL protection rings @@ -541,6 +582,7 @@ COLOUR l_grey POWER 0 POWER2 0 DEPTH 15 +PROTECTIVE RING imperial seal PLURAL imperial seals @@ -561,7 +603,7 @@ RARITY 75 ASCII '%' UTF8 "%" COLOUR brown -POWER 0 +POWER 1500 POWER2 0 DEPTH 1 STACKABLE @@ -573,7 +615,7 @@ RARITY 75 ASCII '%' UTF8 "%" COLOUR yellow -POWER 0 +POWER 1500 POWER2 0 DEPTH 1 STACKABLE @@ -585,7 +627,7 @@ RARITY 85 ASCII '%' UTF8 "%" COLOUR white -POWER 0 +POWER 1500 POWER2 0 DEPTH 1 STACKABLE diff --git a/display-nc.cc b/display-nc.cc index 1d9ad51..790b52b 100644 --- a/display-nc.cc +++ b/display-nc.cc @@ -157,13 +157,13 @@ Gamecolour decal_colours[NUM_DECALS] = cchar_t terrain_tiles[NUM_TERRAINS]; /*! \brief ncursesw objects for drawing items */ -cchar_t permobj_tiles[NUM_OF_PERMOBJS]; +cchar_t *permobj_tiles; #define DISP_HEIGHT (DISP_NC_SIDE) #define DISP_WIDTH (DISP_NC_SIDE) /*! \brief ncursesw objects for drawing monsters */ -cchar_t permon_tiles[NUM_OF_PERMONS]; +cchar_t *permon_tiles; /*! \brief ncursesw object for drawing unexplored space */ cchar_t blank_tile; @@ -190,6 +190,39 @@ char const *damtype_names[DT_COUNT] = { "drowning", }; +static void announce_role(Role_id role) +{ + switch (role) + { + case Role_none: + print_msg(Msg_prio::Bug, "\nBUG: no role selected.\n\n"); + break; + case Role_princess: + print_msg("\nYou are the rightful heir to your mother's throne,\n"); + print_msg("and have been banished to this strange, shadowy\n"); + print_msg("realm by your father's treacherous bastard. The\n"); + print_msg("power of the royal house's magic echoes in your\n"); + print_msg("soul, though you have little hope of directing it\n"); + print_msg("well without the proper talismans.\n\n"); + break; + case Role_demon_hunter: + print_msg("\nWhile fighting the members of a devil-cult, you were\n"); + print_msg("banished to this strange, shadowy realm by the foul\n"); + print_msg("sorcery of the cult leader. You imagine your training\n"); + print_msg("will serve you well here, for what could be a more\n"); + print_msg("obvious home for demons than a realm to which a cultist\n"); + print_msg("would banish you?\n\n"); + break; + case Role_thanatophile: + print_msg("\nPursuing a notorious necromancer, you stumbled on a\n"); + print_msg("magical trap in his lair and found yourself banished\n"); + print_msg("to this strange, shadowy realm. The austere arts of\n"); + print_msg("beloved Death will serve well should you encounter the\n"); + print_msg("walking dead in this terrible place.\n\n"); + break; + } +} + /*! \brief Draw the main menu. */ static void draw_main_menu(void) { @@ -433,17 +466,14 @@ static void load_unicode_tiles(void) static void load_ascii_tiles(void) { int i; - { - wchar_t wch[2]; - wch[0] = L'@'; - wch[1] = 0; - setcchar(&player_tile, wch, 0, 0, nullptr); - wch[0] = L' '; - setcchar(&blank_tile, wch, 0, 0, nullptr); - } + wchar_t wch[2]; + wch[0] = L'@'; + wch[1] = 0; + setcchar(&player_tile, wch, 0, 0, nullptr); + wch[0] = L' '; + setcchar(&blank_tile, wch, 0, 0, nullptr); for (i = 0; i < NUM_OF_PERMONS; ++i) { - wchar_t wch[2]; wch[0] = permons[i].sym; wch[1] = 0; setcchar(permon_tiles + i, wch, @@ -452,7 +482,6 @@ static void load_ascii_tiles(void) } for (i = 0; i < NUM_TERRAINS; ++i) { - wchar_t wch[2]; wch[0] = terrain_props[i].ascii; wch[1] = 0; setcchar(terrain_tiles + i, wch, @@ -461,7 +490,6 @@ static void load_ascii_tiles(void) } for (i = 0; i < NUM_OF_PERMOBJS; ++i) { - wchar_t wch[2]; wch[0] = permobjs[i].sym; wch[1] = 0; setcchar(permobj_tiles + i, wch, @@ -575,6 +603,8 @@ int launch_user_interface(void) init_pair(Gcol_d_grey, COLOR_BLACK, COLOR_BLACK); init_pair(Gcol_purple, COLOR_MAGENTA, COLOR_BLACK); init_pair(Gcol_cyan, COLOR_CYAN, COLOR_BLACK); + permon_tiles = new cchar_t[NUM_OF_PERMONS]; + permobj_tiles = new cchar_t[NUM_OF_PERMOBJS]; if (force_ascii) { load_ascii_tiles(); @@ -974,6 +1004,7 @@ Pass_fail select_dir(Offset *pstep) break; case '\x1b': case ' ': + print_msg("Cancelled.\n"); return You_fail; /* cancelled. */ default: print_msg("Bad direction (use movement keys).\n"); @@ -1385,8 +1416,31 @@ void get_player_action(Action *act) */ int display_shutdown(void) { + delete[] permobj_tiles; + permobj_tiles = nullptr; + delete[] permon_tiles; + permon_tiles = nullptr; + hide_panel(status_panel); + hide_panel(world_panel); + hide_panel(message_panel); + hide_panel(inventory_panel); + hide_panel(fullscreen_panel); + update_panels(); + doupdate(); clear(); refresh(); + del_panel(status_panel); + del_panel(world_panel); + del_panel(message_panel); + del_panel(inventory_panel); + del_panel(fullscreen_panel); + delwin(status_window); + delwin(world_window); + delwin(message_window); + delwin(inventory_window); + delwin(fullscreen_window); + status_panel = world_panel = message_panel = inventory_panel = fullscreen_panel = nullptr; + status_window = world_window = message_window = inventory_window = fullscreen_window = nullptr; endwin(); return 0; } @@ -1410,8 +1464,10 @@ int getYN(char const *msg) ch = wgetch(message_window); if (ch == 'Y') { + print_msg("Confirmed.\n"); return 1; } + print_msg("Cancelled.\n"); return 0; } @@ -1436,6 +1492,7 @@ int getyn(char const *msg) return 0; case '\x1b': case ' ': + print_msg("Cancelled.\n"); return -1; default: print_msg("Invalid response. Press y or n (ESC or space to cancel)\n"); @@ -1553,6 +1610,8 @@ static void show_game_view(void) static void run_main_menu(void) { bool done = false; + bool role_selected = false; + Role_id role = Role_princess; char name[16]; do { @@ -1564,7 +1623,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. Remind me of your name?\n"); + mvwprintw(fullscreen_window, 13, 19, "Welcome. Remind me of thy name?\n"); mvwprintw(fullscreen_window, 14, 1, "\n"); wmove(fullscreen_window, 14, 30); update_panels(); @@ -1574,11 +1633,41 @@ static void run_main_menu(void) mvwgetnstr(fullscreen_window, 14, 30, name, 16); curs_set(0); noecho(); - new_game(name); + mvwprintw(fullscreen_window, 15, 19, "What path dost thou follow in thy quest?\n"); + mvwprintw(fullscreen_window, 16, 25, "(P)rincess\n"); + mvwprintw(fullscreen_window, 17, 25, "(D)emon Hunter\n"); + mvwprintw(fullscreen_window, 18, 25, "(T)hanatophile\n"); + role_selected = false; + do + { + int ch = wgetch(fullscreen_window); + switch (ch) + { + case 'p': + case 'P': + role_selected = true; + role = Role_princess; + break; + case 'd': + case 'D': + role_selected = true; + role = Role_demon_hunter; + break; + case 't': + case 'T': + role_selected = true; + role = Role_thanatophile; + break; + default: + break; + } + } while (!role_selected); + new_game(name, role); done = true; wclear(message_window); wmove(message_window, 0, 0); show_game_view(); + announce_role(role); break; case 'c': case 'C': @@ -1588,20 +1677,25 @@ static void run_main_menu(void) case 'R': // TODO implement loading properly. { - int i; wclear(message_window); wmove(message_window, 0, 0); - i = load_game(); - if (i != -1) - { - done = true; - } - else - { - mvwprintw(fullscreen_window, 14, 30, "No saved game found.\n"); + try + { + load_game(); + done = true; + } + catch (Obumb_sysexcep e) + { + mvwprintw(fullscreen_window, 14, 20, "Error opening saved game:\n"); + mvwprintw(fullscreen_window, 15, 1, "%s\n", strerror(e.err)); + } + catch (Obumb_dataexcep e) + { + mvwprintw(fullscreen_window, 14, 20, "Error loading saved game:\n"); + mvwprintw(fullscreen_window, 15, 1, "%s\n", e.str); update_panels(); doupdate(); - } + } } break; case 'q': @@ -1759,5 +1853,6 @@ void print_mon_name(Mon_handle mon, int article) free(s); } + /* display-nc.cc */ // vim:cindent diff --git a/log.cc b/log.cc index c4cf754..5de2342 100644 --- a/log.cc +++ b/log.cc @@ -48,29 +48,34 @@ static void rebuild_mapmons(void); int datadir_fd; int confdir_fd; -/*! \brief call system(cmd) and throw an exception on failure - * - * \todo throw a proper exception - */ +/*! \brief call system(cmd) and throw an exception on failure */ void wrapped_system(char const *cmd) { int i = system(cmd); if (i != 0) { - throw(i); + throw Obumb_sysexcep(errno); } } -/*! \brief call fread(args) and throw an exception on failure - * - * \todo throw a proper exception - */ +/*! \brief call fread(args) and throw an exception on failure */ void wrapped_fread(void *buf, size_t size, size_t nmemb, FILE *fp) { size_t i = fread(buf, size, nmemb, fp); if (i != nmemb) { - throw(i); + if (ferror(fp)) + { + throw Obumb_sysexcep(errno); + } + else if (feof(fp)) + { + throw Obumb_dataexcep("Unexpected end of file"); + } + else + { + throw Obumb_sysexcep(errno); + } } } @@ -195,6 +200,23 @@ static inline void serialize_monhandle(FILE *fp, Mon_handle m) fwrite(&tmp, 1, sizeof tmp, fp); } +/*! \brief Throw exception if monster handle invalid */ +static void check_monhandle(Mon_handle mon) +{ + if (mon >= first_free_mon_handle) + { + throw Obumb_dataexcep("Object handle >= first free object handle - save game probably corrupt"); + } +} + +static void check_pmref(int pm) +{ + if ((pm < 0) || (pm >= NUM_OF_PERMONS)) + { + throw Obumb_dataexcep("Monster permon ID out of bounds - save game probably corrupt"); + } +} + /*! \brief Read an object handle */ static inline void deserialize_objhandle(FILE *fp, Obj_handle *o) { @@ -210,11 +232,30 @@ static inline void serialize_objhandle(FILE *fp, Obj_handle o) fwrite(&tmp, 1, sizeof tmp, fp); } +static void check_poref(int po) +{ + if ((po < 0) || (po >= NUM_OF_PERMOBJS)) + { + throw Obumb_dataexcep("Object permobj ID out of bounds - save game probably corrupt"); + } +} + +/*! \brief Throw exception if object handle invalid */ +static void check_objhandle(Obj_handle obj) +{ + if (obj >= first_free_obj_handle) + { + throw Obumb_dataexcep("Object handle >= first free object handle - save game probably corrupt"); + } +} + /*! \brief Read a monster */ static void deserialize_monster(FILE *fp, Mon * mon) { deserialize_monhandle(fp, &(mon->self)); + check_monhandle(mon->self); deserialize_int(fp, &(mon->pm_ref)); + check_pmref(mon->pm_ref); deserialize_uint32(fp, &(mon->flags)); deserialize_coord(fp, &(mon->pos)); deserialize_coord(fp, &(mon->ai_lastpos)); @@ -250,7 +291,9 @@ static void serialize_monster(FILE *fp, Mon const& mon) static void deserialize_object(FILE *fp, Obj *obj) { deserialize_objhandle(fp, &(obj->self)); + check_objhandle(obj->self); deserialize_int(fp, &(obj->po_ref)); + check_poref(obj->po_ref); deserialize_uint32(fp, &(obj->flags)); deserialize_coord(fp, &(obj->pos)); deserialize_int(fp, &(obj->quan)); @@ -279,9 +322,12 @@ static void serialize_object(FILE *fp, Obj const& obj) /*! \brief Read the player */ static void deserialize_player(FILE *fp, Player *player) { + int32_t tmp; deserialize_cstring(fp, player->name, 16); deserialize_monhandle(fp, &(player->mh)); deserialize_coord(fp, &(player->pos)); + deserialize_int(fp, &tmp); + player->role = (Role_id) tmp; deserialize_int(fp, &(player->body)); deserialize_int(fp, &(player->bdam)); deserialize_int(fp, &(player->agility)); @@ -304,10 +350,14 @@ static void deserialize_player(FILE *fp, Player *player) for (int i = 0; i < INVENTORY_SIZE; ++i) { deserialize_objhandle(fp, &(player->inventory[i])); + check_objhandle(player->inventory[i]); } deserialize_objhandle(fp, &(player->weapon)); + check_objhandle(player->weapon); deserialize_objhandle(fp, &(player->armour)); + check_objhandle(player->armour); deserialize_objhandle(fp, &(player->ring)); + check_objhandle(player->ring); for (int i = 0; i < TOTAL_FELL_POWERS; ++i) { deserialize_int(fp, &(player->sympathy[i])); @@ -320,6 +370,7 @@ static void serialize_player(FILE *fp, Player const& player) serialize_cstring(fp, player.name, 16); serialize_monhandle(fp, player.mh); serialize_coord(fp, player.pos); + serialize_int(fp, (int) player.role); serialize_int(fp, player.body); serialize_int(fp, player.bdam); serialize_int(fp, player.agility); @@ -368,6 +419,9 @@ void save_game(void) close(fd); return; } + serialize_uint32(fp, OBUMBRATA_MAGIC_NUMBER); + serialize_uint32(fp, MAJVERS); + serialize_uint32(fp, MINVERS); serialize_int(fp, depth); serialize_int(fp, game_tick); serialize_objhandle(fp, first_free_obj_handle); @@ -418,70 +472,138 @@ static void rebuild_mapobjs(void) } } -/*! \brief Reload and unlink a saved game */ -int load_game(void) +static void check_magic(uint32_t magic) { - int fd; - FILE *fp; - fd = openat(datadir_fd, "obumbrata.sav", O_RDONLY, 0); - if (fd == -1) + if (magic != OBUMBRATA_MAGIC_NUMBER) { - return -1; + throw Obumb_dataexcep("Invalid file ID code"); } - fp = fdopen(fd, "rb"); - if (!fp) +} + +static void check_minvers(uint32_t minvers) +{ + if (minvers != MINVERS) + { + throw Obumb_dataexcep("Minor-version mismatch"); + } +} + +static void check_majvers(uint32_t majvers) +{ + if (majvers != MAJVERS) { - return -1; + throw Obumb_dataexcep("Major-version mismatch"); } - deserialize_int(fp, &depth); - deserialize_int(fp, &game_tick); - deserialize_objhandle(fp, &first_free_obj_handle); - deserialize_monhandle(fp, &first_free_mon_handle); - deserialize_player(fp, &u); - deserialize_level(fp, &lvl); - while (1) +} + +/*! \brief Reload and unlink a saved game + * + * Any read errors or probable data corruption encountered during loading will + * be reported as an exception. */ +void load_game(void) +{ + int fd = -1; + FILE *fp = nullptr; + uint32_t tmp; + try { - Mon_handle mon; - deserialize_monhandle(fp, &mon); - if (mon != NO_MON) + fd = openat(datadir_fd, "obumbrata.sav", O_RDONLY, 0); + if (fd == -1) { - Mon m; - deserialize_monster(fp, &m); - monsters[mon] = m; + throw Obumb_sysexcep(errno); } - else + fp = fdopen(fd, "rb"); + if (!fp) { - break; + throw Obumb_sysexcep(errno); } - } - while (1) - { - Obj_handle obj; - deserialize_objhandle(fp, &obj); - if (obj != NO_OBJ) + deserialize_uint32(fp, &tmp); + check_magic(tmp); + deserialize_uint32(fp, &tmp); + check_majvers(tmp); + deserialize_uint32(fp, &tmp); + check_minvers(tmp); + deserialize_int(fp, &depth); + deserialize_int(fp, &game_tick); + deserialize_objhandle(fp, &first_free_obj_handle); + if (first_free_obj_handle == 0) { - Obj o; - deserialize_object(fp, &o); - objects[obj] = o; + throw Obumb_dataexcep("Object pool exhausted - save file probably corrupt"); } - else + deserialize_monhandle(fp, &first_free_mon_handle); + if (first_free_mon_handle == 0) { - break; + throw Obumb_dataexcep("Monster pool exhausted - save file probably corrupt"); + } + deserialize_player(fp, &u); + deserialize_level(fp, &lvl); + while (1) + { + Mon_handle mon; + deserialize_monhandle(fp, &mon); + check_monhandle(mon); + if (mon != NO_MON) + { + Mon m; + deserialize_monster(fp, &m); + monsters[mon] = m; + } + else + { + break; + } } + while (1) + { + Obj_handle obj; + deserialize_objhandle(fp, &obj); + check_objhandle(obj); + if (obj != NO_OBJ) + { + Obj o; + deserialize_object(fp, &o); + objects[obj] = o; + } + else + { + break; + } + } + rebuild_mapobjs(); + rebuild_mapmons(); + u.mptr = mon_snapv(u.mh); + fclose(fp); + game_finished = false; + look_around_you(); + notify_load_complete(); + int i = unlinkat(datadir_fd, "obumbrata.sav", 0); + if (i == -1) + { + debug_save_unlink_failed(); + } + return; } - rebuild_mapobjs(); - rebuild_mapmons(); - u.mptr = mon_snapv(u.mh); - fclose(fp); - game_finished = false; - look_around_you(); - notify_load_complete(); - int i = unlinkat(datadir_fd, "obumbrata.sav", 0); - if (i == -1) + catch (Obumb_dataexcep e) { - debug_save_unlink_failed(); + /* NOTE: if we got a dataexcep, we've definitely opened fp, so no need + * to check for null pointer */ + fclose(fp); + game_cleanup(); + throw; + } + catch (Obumb_sysexcep e) + { + if (fp) + { + fclose(fp); + } + else if (fd != -1) + { + close(fd); + } + game_cleanup(); + throw; } - return 0; } /*! \brief Write human-readable snapshot of the player character diff --git a/main.cc b/main.cc index 25d305d..d42e244 100644 --- a/main.cc +++ b/main.cc @@ -51,16 +51,18 @@ void game_cleanup(void) { depth = 0; game_tick = 0; + first_free_obj_handle = 1u; + first_free_mon_handle = 1u; level_cleanup(); player_cleanup(); } -void new_game(char const *name) +void new_game(char const *name, Role_id role) { game_finished = false; depth = 1; game_tick = 0; - u_init(name); + u_init(name, role); make_new_level(); } @@ -126,8 +128,7 @@ void main_loop(void) game_cleanup(); } -/*! \brief main() - */ +/*! \brief main() */ int main(void) { load_config(); diff --git a/map.cc b/map.cc index a132974..c715194 100644 --- a/map.cc +++ b/map.cc @@ -294,9 +294,9 @@ void make_new_level(void) { build_level(); populate_level(); + notify_change_of_depth(); inject_player(&lvl, lvl.self.naive_prev()); look_around_you(); - notify_change_of_depth(); } /*! \brief Random walk which grows the level @@ -644,25 +644,26 @@ void look_around_you(void) /*! \brief Description of terrain types */ Terrain_props terrain_props[NUM_TERRAINS] = { - { "cave wall", '#', "█", Gcol_brown, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile }, - { "masonry wall", '#', "█", Gcol_l_grey, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile }, - { "amethyst wall", '#', "█", Gcol_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile }, - { "iron wall", '#', "█", Gcol_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile }, - { "skin wall", '#', "█", Gcol_l_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile }, - { "bone wall", '#', "█", Gcol_white, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile }, - { "door", '+', "+", Gcol_l_grey, TFLAG_opaque | TFLAG_block_missile }, + { "cave wall", '#', "█", Gcol_brown, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items }, + { "masonry wall", '#', "█", Gcol_l_grey, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items }, + { "amethyst wall", '#', "█", Gcol_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items }, + { "iron wall", '#', "█", Gcol_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items }, + { "skin wall", '#', "█", Gcol_l_purple, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items }, + { "bone wall", '#', "█", Gcol_white, TFLAG_opaque | TFLAG_block_beings | TFLAG_block_missile | TFLAG_block_items }, + { "door", '+', "+", Gcol_l_grey, TFLAG_opaque | TFLAG_block_missile | TFLAG_block_items }, { "floor", '.', "·", Gcol_l_grey, TFLAG_floor }, { "amethyst floor", '.', "·", Gcol_purple, TFLAG_floor }, { "iron floor", '.', "·", Gcol_iron, TFLAG_floor }, { "skin floor", '.', "·", Gcol_l_purple, TFLAG_floor }, { "bone floor", '.', "·", Gcol_white, TFLAG_floor }, - { "altar", '_', "_", Gcol_l_grey, 0 }, - { "upward stairs", '<', "<", Gcol_l_grey, TFLAG_ascend }, - { "downward stairs", '>', ">", Gcol_l_grey, TFLAG_descend }, - { "inert portal", '^', "☊", Gcol_d_grey, TFLAG_portal | TFLAG_ascend }, - { "active portal", '^', "☊", Gcol_white, TFLAG_portal | TFLAG_descend }, - { "lava", '}', "≈", Gcol_red, TFLAG_fire_hazard }, - { "water", '}', "≈", Gcol_blue, TFLAG_drown_hazard }, + { "altar", '_', "_", Gcol_l_grey, 0 | TFLAG_block_items }, + { "upward stairs", '<', "<", Gcol_l_grey, TFLAG_ascend | TFLAG_block_items }, + { "downward stairs", '>', ">", Gcol_l_grey, TFLAG_descend | TFLAG_block_items }, + { "inert portal", '^', "☊", Gcol_d_grey, TFLAG_portal | TFLAG_ascend | TFLAG_block_items }, + { "active portal", '^', "☊", Gcol_white, TFLAG_portal | TFLAG_descend | TFLAG_block_items }, + { "chasm", ':', "↓", Gcol_d_grey, TFLAG_fall_hazard | TFLAG_block_items }, + { "lava", '}', "≈", Gcol_red, TFLAG_fire_hazard | TFLAG_block_items }, + { "water", '}', "≈", Gcol_blue, TFLAG_drown_hazard | TFLAG_block_items }, }; /*! \brief self-explanatory */ @@ -671,6 +672,27 @@ bool terrain_is_opaque(Terrain terr) return terrain_props[terr].flags & TFLAG_opaque; } +bool terrain_gapes(Terrain terr) +{ + return terrain_props[terr].flags & TFLAG_fall_hazard; +} + +bool terrain_drowns(Terrain terr) +{ + return terrain_props[terr].flags & TFLAG_drown_hazard; +} + +bool terrain_is_hot(Terrain terr) +{ + return terrain_props[terr].flags & TFLAG_fire_hazard; +} + +/*! \brief self-explanatory */ +bool terrain_blocks_items(Terrain terr) +{ + return terrain_props[terr].flags & TFLAG_block_items; +} + /*! \brief self-explanatory */ bool terrain_blocks_beings(Terrain terr) { diff --git a/map.hh b/map.hh index 60a3ff1..78d6875 100644 --- a/map.hh +++ b/map.hh @@ -36,7 +36,7 @@ #include /* XXX enum terrain_num */ enum Terrain { - WALL = 0, MASONRY_WALL, AMETHYST_WALL, IRON_WALL, SKIN_WALL, BONE_WALL, DOOR, FLOOR, AMETHYST_FLOOR, IRON_FLOOR, SKIN_FLOOR, BONE_FLOOR, ALTAR, STAIRS_UP, STAIRS_DOWN, PORTAL_LANDING, PORTAL_ONWARD, LAVA, WATER + WALL = 0, MASONRY_WALL, AMETHYST_WALL, IRON_WALL, SKIN_WALL, BONE_WALL, DOOR, FLOOR, AMETHYST_FLOOR, IRON_FLOOR, SKIN_FLOOR, BONE_FLOOR, ALTAR, STAIRS_UP, STAIRS_DOWN, PORTAL_LANDING, PORTAL_ONWARD, CHASM, LAVA, WATER }; #define MAX_TERRAIN (WATER) #define NUM_TERRAINS (1 + MAX_TERRAIN) @@ -91,6 +91,7 @@ extern Terrain_props terrain_props[NUM_TERRAINS]; #define TFLAG_ascend 0x00000020u #define TFLAG_descend 0x00000040u #define TFLAG_floor 0x00000080u +#define TFLAG_block_items 0x00000100u #define TFLAG_fire_hazard 0x00010000u #define TFLAG_fall_hazard 0x00020000u #define TFLAG_drown_hazard 0x00040000u @@ -368,7 +369,11 @@ void populate_level(void); void inject_player(Level *l, Level_key from); void look_around_you(void); bool terrain_is_opaque(Terrain terr); +bool terrain_is_hot(Terrain terr); +bool terrain_gapes(Terrain terr); +bool terrain_drowns(Terrain terr); bool terrain_blocks_beings(Terrain terr); +bool terrain_blocks_items(Terrain terr); bool terrain_blocks_missiles(Terrain terr); void serialize_level(FILE *fp, Level const *l); void deserialize_level(FILE *fp, Level *l); diff --git a/mon1.cc b/mon1.cc index 7676b78..1564368 100644 --- a/mon1.cc +++ b/mon1.cc @@ -246,13 +246,10 @@ void unplace_mon(Mon_handle mon) void apply_liquid(int pm, Coord c) { - if (pmon_bleeds(pm)) + Decal_tag decal = pmon_fluid_decal(pm); + if (pm != NO_DECAL) { - lvl.set_decal_at(c, Decal_blood); - } - else if (pmon_suppurates(pm)) - { - lvl.set_decal_at(c, Decal_pus); + lvl.set_decal_at(c, decal); } } @@ -263,11 +260,15 @@ void apply_liquid(int pm, Coord c) void kill_mon(Mon_handle mon, bool by_you, bool explode_corpse) { Mon *mptr = mon_snapv(mon); + Terrain t = lvl.terrain_at(mptr->pos); int pm = mptr->pm_ref; - apply_liquid(pm, mptr->pos); + if (!terrain_blocks_items(t)) + { + apply_liquid(pm, mptr->pos); + } if (pmon_explodes(pm) || (pmon_leaves_corpse(pm) && explode_corpse)) { - // TODO shower the surroundings with the monster's liquid + detonate_mon(mon); } else if (pmon_leaves_corpse(pm)) { @@ -473,11 +474,33 @@ void summon_demon_near(Coord c) if ((lvl.terrain_at(c2) == FLOOR) && (lvl.mon_at(c2) == NO_MON) && (c2 != u.pos)) { - mon = create_mon(PM_DEMON, c2); - notify_summon_demon(mon); + mon = create_mon(PM_DEMON, c2); + notify_summon_demon(mon); } } +void detonate_mon(Mon_handle mon) +{ + Mon *mptr = mon_snapv(mon); + Coord c; + int pm = mptr->pm_ref; + Decal_tag decal = pmon_fluid_decal(pm); + if (decal != NO_DECAL) + { + for (c.y = mptr->pos.y - 1; c.y <= mptr->pos.y + 1; ++c.y) + { + for (c.x = mptr->pos.x - 1; c.x <= mptr->pos.x + 1; ++c.x) + { + if (lvl.in_bounds(c)) + { + lvl.set_decal_at(c, decal); + } + } + } + } + return; +} + bool mon_visible(Mon_handle mon) { Offset delta; diff --git a/monsters.hh b/monsters.hh index d04a98b..7bdeaa3 100644 --- a/monsters.hh +++ b/monsters.hh @@ -101,6 +101,7 @@ int summoning(Coord c, int how_many); int ood(int power, int ratio); int get_random_pmon(void); void damage_mon(Mon_handle mon, int amount, bool by_you); +void detonate_mon(Mon_handle mon); bool mon_visible(Mon_handle mon); int knockback_mon(Mon_handle mon, Offset step, bool cansee, bool by_you); void move_mon(Mon_handle mon, Coord c); diff --git a/notify-local-tty.cc b/notify-local-tty.cc index 4c909f7..2e60718 100644 --- a/notify-local-tty.cc +++ b/notify-local-tty.cc @@ -222,26 +222,31 @@ void notify_protection_lost(void) print_msg("You feel like you are no longer being helped.\n"); } -void notify_start_lavawalk(void) +void notify_start_hotwalk(Terrain t) { - print_msg("You walk on the lava.\n"); + print_msg("You stride fearlessly across the %s.\n", terrain_props[t].name); } -void notify_blocked_lava(void) +void notify_blocked_hot(Terrain t) { - print_msg("The fierce heat of the molten rock repels you.\n"); + print_msg("The fierce heat of the %s repels you.\n", terrain_props[t].name); } -void notify_start_waterwalk(void) +void notify_start_waterwalk(Terrain t) { - print_msg("You walk on the water.\n"); + print_msg("You stride fearlessly across the surface of the %s.\n", terrain_props[t].name); } -void notify_blocked_water(void) +void notify_blocked_water(Terrain t) { print_msg("The idiot who raised you never taught you to swim.\n"); } +void notify_portal_underfoot(void) +{ + print_msg("There is a stone arch here%s.\n", (lvl.terrain_at(u.pos) == PORTAL_ONWARD) ? ", glowing with arcane power" : ""); +} + void notify_new_obj_at(Coord c, Obj_handle obj) { newsym(c); @@ -899,9 +904,36 @@ void notify_unwield(Obj_handle obj, Noisiness noisy) void notify_wield_weapon(Obj_handle obj) { - print_msg("Wielding "); - print_obj_name(obj); - print_msg(".\n"); + Obj const *optr = obj_snap(obj); + int po = optr->po_ref; + if (permobjs[po].flags[0] & POF_NOTIFY_EQUIP) + { + switch (po) + { + case PO_WINDSWORD: + print_msg("Your stride lightens as you ready the magic blade.\n"); + break; + case PO_HELLGLAIVE: + print_msg("Infernal power suffuses you as you heft the cruel pole-arm.\n"); + break; + case PO_PLAGUE_SCYTHE: + print_msg("The scent of pestilence and decay fills your nostrils as you ready the scythe.\n"); + break; + case PO_DEATH_STAFF: + print_msg("The chill of the grave suffuses you as you ready the black staff.\n"); + break; + case PO_TORMENTORS_LASH: + print_msg("A%s tingle of power runs up your arm as you ready the magic whip.\n", + u.sybaritic() ? " delightful" : "n uncanny"); + break; + } + } + else + { + print_msg("Wielding "); + print_obj_name(obj); + print_msg(".\n"); + } } void notify_weapon_broke(Obj_handle obj) diff --git a/notify.hh b/notify.hh index bbd835f..134bfdb 100644 --- a/notify.hh +++ b/notify.hh @@ -55,11 +55,12 @@ void notify_food_use(int food_use, int hunger_severity); // Resistance notifications // Player movement notifications -void notify_start_lavawalk(void); -void notify_blocked_lava(void); -void notify_start_waterwalk(void); -void notify_blocked_water(void); +void notify_start_hotwalk(Terrain t); +void notify_blocked_hot(Terrain t); +void notify_start_waterwalk(Terrain t); +void notify_blocked_water(Terrain t); void notify_cant_go(void); +void notify_portal_underfoot(void); // Unsorted notifications void notify_new_obj_at(Coord c, Obj_handle obj); @@ -90,6 +91,7 @@ void notify_weapon_broke(Obj_handle obj); void notify_armour_broke(Obj_handle obj); void notify_drop_item(Obj_handle obj); void notify_drop_blocked(void); +void notify_drop_blocked_portal(void); void notify_get_item(Obj_handle from, int slot); void notify_pack_full(void); diff --git a/obj1.cc b/obj1.cc index f240047..5e202d9 100644 --- a/obj1.cc +++ b/obj1.cc @@ -123,12 +123,12 @@ Action_cost eat_food(Obj_handle obj) { Obj *optr = obj_snapv(obj); bool ravenous = (u.food < 0); - u.food += 1500; if (permobjs[optr->po_ref].poclass != POCLASS_FOOD) { debug_eat_non_food(obj); return Cost_none; } + u.food += permobjs[optr->po_ref].power; if (optr->po_ref == PO_DEVIL_SPLEEN) { notify_ingest_spleen(); @@ -280,7 +280,7 @@ Obj_handle create_obj_class_near(enum poclass_num po_class, int quantity, bool w } break; } - return (with_you ? create_obj(po_idx, quantity, with_you, c) : create_obj_near(po_idx, quantity, c)); + return (with_you ? create_obj(po_idx, quantity, true, c) : create_obj_near(po_idx, quantity, c)); } int get_random_pobj(void) @@ -322,16 +322,16 @@ Obj_handle create_corpse(int pm_idx, Coord c) Obj_handle create_obj_near(int po_idx, int quantity, Coord c) { Offset delta; - Coord nc; + Coord nc = c; int panic_count = 200; - while ((lvl.obj_at(c) != NO_OBJ) && (panic_count > 0)) + while ((terrain_blocks_items(lvl.terrain_at(nc)) || (lvl.obj_at(c) != NO_OBJ)) && (panic_count > 0)) { do { delta = random_step(); nc = c + delta; } - while (terrain_blocks_beings(lvl.terrain_at(nc))); + while (terrain_blocks_items(lvl.terrain_at(nc))); c = nc; --panic_count; } @@ -400,8 +400,7 @@ void asprint_obj_name(char **buf, Obj_handle obj) { i = asprintf(buf, "1 %s", poptr->name); } - else if ((poptr->poclass == POCLASS_WEAPON) || - (poptr->poclass == POCLASS_ARMOUR)) + else if (poptr->flags[0] & POF_DAMAGEABLE) { i = asprintf(buf, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR); } @@ -435,8 +434,7 @@ void sprint_obj_name(char *buf, Obj_handle obj, int len) { snprintf(buf, len, "1 %s", poptr->name); } - else if ((poptr->poclass == POCLASS_WEAPON) || - (poptr->poclass == POCLASS_ARMOUR)) + else if (poptr->flags[0] & POF_DAMAGEABLE) { snprintf(buf, len, "a%s %s (%d/%d)", is_vowel(poptr->name[0]) ? "n" : "", poptr->name, optr->durability, OBJ_MAX_DUR); } @@ -462,6 +460,12 @@ void fprint_obj_name(FILE *fp, Obj_handle obj) Action_cost drop_obj(int inv_idx) { Obj *optr; + Terrain t = lvl.terrain_at(u.pos); + if (terrain_props[t].flags & TFLAG_portal) + { + notify_drop_blocked(); + return Cost_none; + } optr = obj_snapv(u.inventory[inv_idx]); if (lvl.obj_at(u.pos) == NO_OBJ) { diff --git a/obumbrata.hh b/obumbrata.hh index c711a55..f0838b4 100644 --- a/obumbrata.hh +++ b/obumbrata.hh @@ -56,10 +56,6 @@ #include "display.hh" #endif -#ifndef NOTIFY_HH -#include "notify.hh" -#endif - #ifndef MAP_HH #include "map.hh" #endif @@ -73,7 +69,7 @@ extern bool game_finished; extern int game_tick; extern bool wizard_mode; Action_cost do_player_action(Action *act); -void new_game(char const *name); +void new_game(char const *name, Role_id role); void main_loop(void); /* XXX misc.c data and funcs */ @@ -84,9 +80,13 @@ extern void log_death(Death d, char const *what); extern void load_config(void); extern void wrapped_system(char const *cmd); extern void wrapped_fread(void *buf, size_t size, size_t nmemb, FILE *stream); -extern int load_game(void); +extern void load_game(void); extern void save_game(void); +#ifndef NOTIFY_HH +#include "notify.hh" +#endif + #endif /* obumbrata.hh */ diff --git a/permobj.hh b/permobj.hh index 20eb221..36bb17b 100644 --- a/permobj.hh +++ b/permobj.hh @@ -56,7 +56,7 @@ enum poclass_num { #include "pobj_id.hh" -#define POBJ_FLAG_WORDS 1 +#define POBJ_FLAG_WORDS 2 // POF field 0 #define POF_NOTIFY_EQUIP 0x00000001u // item has special messages when changing equip status @@ -67,9 +67,26 @@ enum poclass_num { /* Yes, DRESS and MELEE_WEAPON have the same value. This is OK because * only an ARMOUR can possibly be a DRESS, and only a WEAPON can possibly be * a MELEE_WEAPON. */ -#define POF_DRESS 0x00010000u -#define POF_MELEE_WEAPON 0x00010000u -#define POF_RANGED_WEAPON 0x00020000u +#define POF_DRESS 0x00010000u +#define POF_MELEE_WEAPON 0x00010000u +#define POF_RANGED_WEAPON 0x00020000u + +// POF field 1 +#define POF_RES_FIRE 0x00000001u +#define POF_RES_COLD 0x00000002u +#define POF_RES_ELEC 0x00000003u +#define POF_RES_NECRO 0x00000004u +#define POF_RES_POISON 0x00000005u +#define POF_RES_MASK 0x000000ffu +#define POF_DMG_FIRE 0x00000100u +#define POF_DMG_COLD 0x00000200u +#define POF_DMG_ELEC 0x00000300u +#define POF_DMG_NECRO 0x00000400u +#define POF_DMG_POISON 0x00000500u +#define POF_DMG_MASK 0x0000ff00u +#define POF_PASS_WATER 0x00010000u +#define POF_FLIGHT 0x00020000u +#define POF_PROTECTIVE 0x00100000u // all damage from enemy attacks halved /*! \brief The 'permanent object' database */ class Permobj { @@ -89,11 +106,11 @@ public: }; #define NO_POBJ (-1) -extern Permobj permobjs[NUM_OF_PERMOBJS]; +extern const int NUM_OF_PERMOBJS; +extern Permobj permobjs[]; extern bool po_is_stackable(int po); #endif /* permobj.hh */ -// vim:cindent - +// vim:cindent:expandtab diff --git a/permon.hh b/permon.hh index 0e31244..e33fac2 100644 --- a/permon.hh +++ b/permon.hh @@ -95,7 +95,8 @@ public: int speed; /* 0 = slow; 1 = normal; 2 = quick */ uint32_t flags[PERMON_FLAG_FIELDS]; /* resistances, AI settings, etc. */ }; -extern struct Permon permons[NUM_OF_PERMONS]; +extern struct Permon permons[]; +extern int const NUM_OF_PERMONS; #define NO_ATK (-1) #define NO_PMON (-1) @@ -135,6 +136,7 @@ inline bool pmon_bleeds(int pm) { return permons[pm].flags[1] & PMF_CONTAINS_BLO 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; } +Decal_tag pmon_fluid_decal(int pm); #endif diff --git a/player.hh b/player.hh index de73524..bcdf25c 100644 --- a/player.hh +++ b/player.hh @@ -31,6 +31,34 @@ #include "obumbrata.hh" +enum Skill_id +{ + Skill_power_attack, + /* Princess skills */ + Skill_artificer, + Skill_flying_leap, + /* Demon Hunter skills */ + Skill_charge, + Skill_blade_dance, + Skill_demon_slayer, + /* Thanatophile skills */ + Skill_deaths_grace, + Skill_deaths_vengeance +}; + +#define NO_SKILL (-1) +#define LAST_SKILL (Skill_deaths_vengeance) +#define NUM_SKILLS (1 + LAST_SKILL) + +/*! \brief Skill descriptor */ +struct Skill_desc +{ + char const *name; + char const *description; +}; + +extern Skill_desc skill_props[NUM_SKILLS]; + /*! \brief Internal representation of the player character */ #define INVENTORY_SIZE 19 @@ -40,6 +68,7 @@ public: Mon_handle mh; //!< Handle for monster representing the player. Mon *mptr; //!< Pointer for monster representing the player. Coord pos; //!< Position within current dungeon level. + Role_id role; //!< Starting "job"; hypothetically affects gains int body; //!< Combined stamina and strength stat. int bdam; //!< Current level of temporary Body drain int agility; //!< Combined accuracy and avoidance stat. @@ -81,11 +110,14 @@ public: extern Player u; -typedef Action_cost (*deed_func)(Action const *act); +typedef Action_cost (*Deed_func)(Action const *act); + +extern Deed_func deed_funcs[NUM_COMMANDS]; -extern deed_func deed_funcs[NUM_COMMANDS]; +typedef void (*Role_inventory_func)(Player *p); +extern Role_inventory_func role_inventories[NUM_ROLES]; -void u_init(char const *name); +void u_init(char const *name, Role_id role); void write_char_dump(void); int do_death(Death d, char const *what); void heal_u(int amount, int boost, int loud); @@ -106,6 +138,25 @@ bool wielding_melee_weapon(void); bool wielding_ranged_weapon(void); void player_cleanup(void); + +typedef bool (*Action_rewriter_phase1_func)(Action *revisable_action); +extern Action_rewriter_phase1_func action_rewrite_phase1[NUM_COMMANDS]; +extern Combo_phase action_default_combophase[NUM_COMMANDS]; + +typedef Combo_state (*Combo_detector)(std::deque* action_list); +typedef bool (*Combo_avail_func)(Player const *player); + +struct Combo_entry +{ + char const *name; + Game_cmd first_step; //!< if this move isn't present, don't call detector() + Combo_avail_func avail; + Combo_detector detector; +}; + +extern Combo_entry combo_entries[]; + +/*! \brief ID numbers for skills */ #endif /* player.hh */ diff --git a/pmon2.cc b/pmon2.cc index 51ecc99..be9bb80 100644 --- a/pmon2.cc +++ b/pmon2.cc @@ -100,5 +100,18 @@ bool pmon_is_ethereal(int pm) return !!(permons[pm].flags[0] & PMF_ETHEREAL); } +Decal_tag pmon_fluid_decal(int pm) +{ + if (permons[pm].flags[1] & PMF_CONTAINS_BLOOD) + { + return Decal_blood; + } + if (permons[pm].flags[1] & PMF_CONTAINS_PUS) + { + return Decal_pus; + } + return NO_DECAL; +} + /* pmon2.c */ // vim:cindent diff --git a/pmon_comp b/pmon_comp index 0d3576a..51a7f2a 100755 --- a/pmon_comp +++ b/pmon_comp @@ -369,11 +369,10 @@ print "\n"; open(HEADERFILE, ">", "pmon_id.hh") or die "pmon_comp: could not open pmon_id.hh for write: $!"; open(SOURCEFILE, ">", "permons.cc") or die "pmon_comp: could not open permons.cc for write: $!"; print HEADERFILE "// pmon_id.hh\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname\n// then use pmon_comp to regenerate this file and permons.cc\n#pragma once\nenum Pmon_id {\n"; -print SOURCEFILE "// permons.cc\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname then use pmon_comp to\n// regenreate this file and pmon_id.hh\n#include \"core.hh\"\n#include \"permon.hh\"\nPermon permons[NUM_OF_PERMONS] = {\n"; +print SOURCEFILE "// permons.cc\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname then use pmon_comp to\n// regenreate this file and pmon_id.hh\n#include \"core.hh\"\n#include \"permon.hh\"\nPermon permons[] = {\n"; my $phref; my $i; my $total_mons = 0; -my @firsts; my $tagname; for ($i = 0; $i <= $#monsters; ++$i, ++$total_mons) @@ -388,8 +387,8 @@ for ($i = 0; $i <= $#monsters; ++$i, ++$total_mons) printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", %s, %s, Gcol_%s, %d, %d, %d, %d, %d, %d, %d, DT_%s, \"%s\", %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{rarity}, $phref->{power}, $phref->{hp}, $phref->{mtohit}, $phref->{rtohit}, $phref->{mdam}, $phref->{rdam}, $phref->{rdtyp}, $phref->{shootverb}, $phref->{defence}, $phref->{exp}, $phref->{speed}, flag_string($phref->{flags}); } -print SOURCEFILE "};\n// permons.cc\n"; -printf HEADERFILE "};\n".join('', @firsts)."\n#define NUM_OF_PERMONS %d\n\n// pmon_id.hh\n", $total_mons; +printf SOURCEFILE "};\nconst int NUM_OF_PERMONS = %d;\n// permons.cc\n", $total_mons; +printf HEADERFILE "};\n\n// pmon_id.hh\n"; close(SOURCEFILE); close(HEADERFILE); diff --git a/pobj_comp b/pobj_comp index a86e72d..3e89549 100755 --- a/pobj_comp +++ b/pobj_comp @@ -19,6 +19,7 @@ our @rings; our @food; our @carrion; +our $max_flag_index = 1; our %flag_indices = ( 'NOTIFY_EQUIP' => 0, @@ -29,6 +30,19 @@ our %flag_indices = 'DRESS' => 0, 'RANGED_WEAPON' => 0, 'MELEE_WEAPON' => 0, + 'RES_FIRE' => 1, + 'RES_COLD' => 1, + 'RES_ELEC' => 1, + 'RES_NECRO' => 1, + 'RES_POISON' => 1, + 'DMG_FIRE' => 1, + 'DMG_COLD' => 1, + 'DMG_ELEC' => 1, + 'DMG_NECRO' => 1, + 'DMG_POISON' => 1, + 'PASS_WATER' => 1, + 'FLIGHT' => 1, + 'PROTECTIVE' => 1 ); @@ -50,15 +64,15 @@ sub flag_string($) else { my $name; + $#flag_fields = $max_flag_index; + for (my $i = 0; $i <= $max_flag_index; ++$i) + { + $flag_fields[$i] = "0 "; + } for $name (@$aref) { die("Attempt to generate a flag string containing an undefined flag $name!") if !exists($flag_indices{$name}); my $idx = $flag_indices{$name}; - $#flag_fields = $idx if ($idx > $#flag_fields); - if (!defined($flag_fields[$idx])) - { - $flag_fields[$idx] = "0 "; - } $flag_fields[$idx] .= "| POF_$name "; } } @@ -341,7 +355,7 @@ print "\n"; open(HEADERFILE, ">", "pobj_id.hh") or die "pobj_comp: could not open pobj_id.hh for write: $!"; open(SOURCEFILE, ">", "permobj.cc") or die "pobj_comp: could not open permobj.cc for write: $!"; print HEADERFILE "// pobj_id.hh\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname\n// then use pobj_comp to regenerate this file and permobj.cc\n#pragma once\nenum Pobj_id {\n"; -print SOURCEFILE "// permobj.cc\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname then use pobj_comp to\n// regenreate this file and pobj_id.hh\n#include \"core.hh\"\n#include \"permobj.hh\"\nPermobj permobjs[NUM_OF_PERMOBJS] = {\n"; +print SOURCEFILE "// permobj.cc\n// This file is autogenerated from $input_fname\n// and is subject to the same copyright licensing terms as that file.\n// Do not edit this file directly; edit $input_fname then use pobj_comp to\n// regenreate this file and pobj_id.hh\n#include \"core.hh\"\n#include \"permobj.hh\"\nPermobj permobjs[] = {\n"; my $phref; my $i; my $total_objs = 0; @@ -382,7 +396,7 @@ for ($i = 0; $i <= $#armour; ++$i, ++$total_objs) print HEADERFILE ",\n"; } print HEADERFILE " ${tagname}"; - printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_ARMOUR, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags}); + printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_ARMOUR, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags}); } if (defined($tagname)) { @@ -403,7 +417,7 @@ for ($i = 0; $i <= $#food; ++$i, ++$total_objs) print HEADERFILE ",\n"; } print HEADERFILE " ${tagname}"; - printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_FOOD, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags}); + printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_FOOD, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags}); } if (defined($tagname)) { @@ -424,7 +438,7 @@ for ($i = 0; $i <= $#scrolls; ++$i, ++$total_objs) print HEADERFILE ",\n"; } print HEADERFILE " ${tagname}"; - printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_SCROLL, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags}); + printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_SCROLL, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags}); } if (defined($tagname)) { @@ -445,7 +459,7 @@ for ($i = 0; $i <= $#potions; ++$i, ++$total_objs) print HEADERFILE ",\n"; } print HEADERFILE " ${tagname}"; - printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_POTION, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags}); + printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_POTION, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags}); } if (defined($tagname)) { @@ -466,7 +480,7 @@ for ($i = 0; $i <= $#rings; ++$i, ++$total_objs) print HEADERFILE ",\n"; } print HEADERFILE " ${tagname}"; - printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_RING, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags}); + printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_RING, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags}); } if (defined($tagname)) { @@ -487,7 +501,7 @@ for ($i = 0; $i <= $#carrion; ++$i, ++$total_objs) print HEADERFILE ",\n"; } print HEADERFILE " ${tagname}"; - printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_CARRION, %d, %s, %s, Gcol_%s, %d, %d, %d },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags}); + printf SOURCEFILE " { \"%s\", \"%s\", \"%s\", POCLASS_CARRION, %d, %s, %s, Gcol_%s, %d, %d, %d, { %s } },\n", $phref->{name}, $phref->{plural}, $phref->{desc}, $phref->{rarity}, $phref->{ascii}, $phref->{uni}, $phref->{colour}, $phref->{power}, $phref->{power2}, $phref->{depth}, flag_string($phref->{flags}); } if (defined($tagname)) { @@ -495,8 +509,8 @@ if (defined($tagname)) undef $tagname; } -print SOURCEFILE "};\n// permobj.cc\n"; -printf HEADERFILE "};\n".join('', @firsts)."\n#define NUM_OF_PERMOBJS %d\n\n// pobj_id.hh\n", $total_objs; +printf SOURCEFILE "};\nconst int NUM_OF_PERMOBJS = %d;\n// permobj.cc\n", ${total_objs}; +print HEADERFILE "};\n".join('', @firsts)."\n\n// pobj_id.hh\n"; close(SOURCEFILE); close(HEADERFILE); diff --git a/role.cc b/role.cc new file mode 100644 index 0000000..7de3e00 --- /dev/null +++ b/role.cc @@ -0,0 +1,76 @@ +/*! \file role.cc + * \brief Per-role layer-character initialization/advancement + */ + +/* + * Copyright 2014 Martin Read + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * 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. + */ + +#include "obumbrata.hh" +#include "combat.hh" +#include "objects.hh" +#include +#include +#include +#include + +static void princess_inventory(Player *p) +{ + p->inventory[0] = create_obj(PO_DAGGER, 1, true, Nowhere); + p->inventory[1] = create_obj(PO_IRON_RATION, 1, true, Nowhere); + p->inventory[2] = create_obj(PO_BATTLE_BALLGOWN, 1, true, Nowhere); + p->weapon = p->inventory[0]; + p->ring = NO_OBJ; + p->armour = p->inventory[2]; +} + +static void demonhunter_inventory(Player *p) +{ + p->inventory[0] = create_obj(PO_MACE, 1, true, Nowhere); + p->inventory[1] = create_obj(PO_IRON_RATION, 1, true, Nowhere); + p->inventory[2] = create_obj(PO_LEATHER_ARMOUR, 1, true, Nowhere); + p->weapon = p->inventory[0]; + p->ring = NO_OBJ; + p->armour = p->inventory[2]; +} + +static void thanatophile_inventory(Player *p) +{ + p->inventory[0] = create_obj(PO_MACE, 1, true, Nowhere); + p->inventory[1] = create_obj(PO_IRON_RATION, 1, true, Nowhere); + p->inventory[2] = create_obj(PO_LEATHER_ARMOUR, 1, true, Nowhere); + p->weapon = p->inventory[0]; + p->ring = NO_OBJ; + p->armour = p->inventory[2]; +} + +Role_inventory_func role_inventories[NUM_ROLES] = +{ + princess_inventory, + demonhunter_inventory, + thanatophile_inventory +}; + +/* role.cc */ +// vim:cindent:expandtab diff --git a/skills.cc b/skills.cc new file mode 100644 index 0000000..0198b82 --- /dev/null +++ b/skills.cc @@ -0,0 +1,88 @@ +/*! \file skills.cc + * \brief Player-character skill data + */ + +/* + * Copyright © 2014 Martin Read + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * 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. + */ + +#include "obumbrata.hh" +#include "combat.hh" +#include "objects.hh" +#include "player.hh" +#include +#include +#include +#include + +/*! \brief Descriptions of the player character's possible skills */ +Skill_desc skill_props[NUM_SKILLS] = +{ + { + "Power Attack", + ("On the turn after you use the 'wait' command, any melee attack you " + "make will hit automatically and do 75 percent more damage than " + "normal."), + }, + { + "Flying Leap", + ("While wearing a dress, robe, or leather armour, you can jump across " + "any one-square environmental hazard (lava, chasms, etc.) that has " + "clear space on the other side by attempting to move onto it."); + }, + { + "Charge", + ("If you move in the same direction on two (or more) consecutive " + "turns, any melee attack you make in that direction on the " + "immediately following turn will hit automatically and do more " + "damage than usual."), + }, + { + "Blade Dance", + ("While wielding a sword or dagger, if you move diagonally past an " + "enemy, you will make a free melee attack against that enemy; this " + "attack will have normal accuracy and damage. Moving diagonally " + "between two enemies will randomly select one of them to be the " + "victim of this attack."), + }, + { + "Demon Slayer", + ("Your instincts for dealing with demonkind have developed to the " + "point that your melee attacks do double damage against demons."), + }, + { + "Death's Grace", + ("Beloved Death has granted you partial protection from the malign " + "arts of necromancers. You take greatly reduced damage from " + "necromantic magic."), + }, + { + "Death's Vengeance", + ("Your melee attacks are imbued with the blessing of beloved Death, " + "causing them to do double damage against the undead."), + }, +}; + +/* skills.cc */ +// vim:cindent:expandtab diff --git a/u.cc b/u.cc index 48cda87..a90f301 100644 --- a/u.cc +++ b/u.cc @@ -35,7 +35,6 @@ #include #include -bool action_rewrite(Action const *act, Action *revised_act); struct Player u; bool wielding_ranged_weapon(void) @@ -120,72 +119,65 @@ Action_cost move_player(Offset delta) } return player_attack(delta); } - switch (lvl.terrain_at(c)) + Terrain t = lvl.terrain_at(c); + if (terrain_blocks_beings(t)) { - case WALL: - case MASONRY_WALL: - case AMETHYST_WALL: - case IRON_WALL: - case SKIN_WALL: - case BONE_WALL: notify_cant_go(); return Cost_none; - case FLOOR: - case AMETHYST_FLOOR: - case IRON_FLOOR: - case SKIN_FLOOR: - case BONE_FLOOR: - case DOOR: - case STAIRS_UP: - case STAIRS_DOWN: - case PORTAL_LANDING: - case PORTAL_ONWARD: - case ALTAR: - reloc_player(c); - return Cost_std; - case LAVA: + } + else if (terrain_is_hot(t)) + { if (u.resistances[DT_FIRE]) { - if (lvl.terrain_at(u.pos) != LAVA) + if (!terrain_is_hot(lvl.terrain_at(u.pos))) { - notify_start_lavawalk(); + notify_start_hotwalk(t); } - reloc_player(c); - return Cost_std; } else { - notify_blocked_lava(); + notify_blocked_hot(t); return Cost_none; } - case WATER: + } + else if (terrain_drowns(t)) + { if (u.ring != NO_OBJ) { Obj const *ring = obj_snap(u.ring); if (ring->po_ref == PO_FROST_RING) { - if (lvl.terrain_at(u.pos) != WATER) + if (!terrain_drowns(lvl.terrain_at(u.pos))) { - notify_start_waterwalk(); + notify_start_waterwalk(t); } } - reloc_player(c); - return Cost_std; } else { - notify_blocked_water(); + notify_blocked_water(t); return Cost_none; } } - return Cost_none; + else if (terrain_gapes(t)) + { + notify_cant_go(); + return Cost_none; + } + reloc_player(c); + return Cost_std; } void reloc_player(Coord c) { + Terrain t = lvl.terrain_at(c); Coord oc = u.pos; u.pos = c; look_around_you(); + if (terrain_props[t].flags & TFLAG_portal) + { + notify_portal_underfoot(); + } if (lvl.obj_at(c) != NO_OBJ) { notify_obj_at(c); @@ -337,7 +329,7 @@ int do_death(Death d, char const *what) return really; } -void u_init(char const *name) +void u_init(char const *name, Role_id role) { if (!name || !*name) { @@ -360,12 +352,7 @@ void u_init(char const *name) { u.inventory[i] = NO_OBJ; } - u.inventory[0] = create_obj(PO_DAGGER, 1, true, Nowhere); - u.inventory[1] = create_obj(PO_IRON_RATION, 1, true, Nowhere); - u.inventory[2] = create_obj(PO_BATTLE_BALLGOWN, 1, true, Nowhere); - u.weapon = u.inventory[0]; - u.ring = NO_OBJ; - u.armour = u.inventory[2]; + role_inventories[role](&u); recalc_defence(); } @@ -539,14 +526,6 @@ bool Player::resists(Damtyp dtype) const /*! \brief Action history for combo detection */ std::deque past_actions; -/*! \brief Constants used in combo detection logic */ -enum Combo_phase -{ - Combo_invalid, - Combo_valid, - Combo_finisher -}; - /* \brief Process action combos * * This function is responsible for detecting whether the most recently @@ -560,117 +539,67 @@ enum Combo_phase * \return true if revised_act was written to; false otherwise */ -bool action_rewrite(Action const *act, Action *revised_act) +static bool action_rewrite(Action const *act, Action *revised_act) { Coord c = u.pos; - Mon_handle mon = NO_MON; - Offset o; Action tmp_act = *act; Combo_phase p; bool rewrite_flag = false; - switch (tmp_act.cmd) + if (tmp_act.cmd == REJECTED_ACTION) { - case STAND_STILL: - p = Combo_valid; - break; - case ATTACK: - o.y = tmp_act.details[0]; - o.x = tmp_act.details[1]; - c = u.pos + o; - if (!lvl.in_bounds(c)) - { - debug_move_oob(c); // TODO notify_attack_oob() - tmp_act.cmd = REJECTED_ACTION; - rewrite_flag = true; - p = Combo_invalid; - break; - } - mon = lvl.mon_at(c); - p = Combo_finisher; - break; - case WALK: - o.y = tmp_act.details[0]; - o.x = tmp_act.details[1]; - c = u.pos + o; - if (!lvl.in_bounds(c)) - { - debug_move_oob(c); - tmp_act.cmd = REJECTED_ACTION; - rewrite_flag = true; - p = Combo_invalid; - } - else if (lvl.mon_at(c) != NO_MON) - { - mon = lvl.mon_at(c); - tmp_act.cmd = ATTACK; - rewrite_flag = true; - p = Combo_finisher; - } - else - { - p = Combo_valid; - } - break; - default: - rewrite_flag = false; - p = Combo_invalid; - break; + return false; } + if (action_rewrite_phase1[tmp_act.cmd] != nullptr) + { + rewrite_flag = action_rewrite_phase1[tmp_act.cmd](&tmp_act); + } + p = action_default_combophase[tmp_act.cmd]; switch (p) { case Combo_invalid: + past_actions.clear(); break; case Combo_valid: - if (past_actions.empty()) - { - past_actions.push_front(tmp_act); - } - else + case Combo_finisher: { - switch (tmp_act.cmd) + Combo_state cs = Combo_state_none; + Combo_state tmp_cs = Combo_state_none; + past_actions.push_back(tmp_act); + do { - case STAND_STILL: - /* For now, no combo opens with more than one stand still, - * and no combo starts with something else then chains through - * a stand still */ - if (past_actions.front().cmd != STAND_STILL) + for (int i = 0; combo_entries[i].first_step != REJECTED_ACTION; ++i) { - past_actions.clear(); - past_actions.push_front(tmp_act); + if (past_actions[0].cmd == combo_entries[i].first_step) + { + if (combo_entries[i].avail(&u)) + { + tmp_cs = combo_entries[i].detector(&past_actions); + if (tmp_cs == Combo_state_complete) + { + tmp_act = past_actions[0]; + rewrite_flag = true; + cs = Combo_state_complete; + break; + } + if (tmp_cs == Combo_state_partial) + { + cs = Combo_state_partial; + } + } + } } - break; - case WALK: - /* For now, no combo chains through a WALK, but the charge - * combo will when I have the mental effort to write it. */ - past_actions.clear(); - break; - default: - DEBUG_CONTROL_FLOW(); - break; - } - } - break; - case Combo_finisher: - if (!past_actions.empty()) - { - switch (past_actions.front().cmd) - { - case STAND_STILL: - if ((empty_handed() || wielding_melee_weapon()) && - (mon != NO_MON)) + /* If we didn't even get a partial match, drop the oldest + * action and try again. */ + if (cs == Combo_state_none) { - rewrite_flag = true; - tmp_act.cmd = CMD_POWER_ATTACK; + past_actions.pop_front(); } - break; - case WALK: - break; - default: - DEBUG_CONTROL_FLOW(); - break; + } while ((cs == Combo_state_none) && (past_actions.size() >= 1)); + if ((p == Combo_finisher) && (cs != Combo_state_complete)) + { + past_actions.clear(); } } - past_actions.clear(); break; } if (rewrite_flag) @@ -682,7 +611,7 @@ bool action_rewrite(Action const *act, Action *revised_act) /*! \brief Process a player action. * - * \todo Make CMD_POWER_ATTACK do something special + * \todo Make COMBO_POWATK do something special */ Action_cost do_player_action(Action *act) { @@ -713,6 +642,8 @@ void player_cleanup(void) int i; memset(u.sympathy, '\0', sizeof u.sympathy); memset(u.resistances, '\0', sizeof u.resistances); + u.mptr = nullptr; + u.mh = NO_MON; u.experience = u.level = 0; u.body = u.agility = u.bdam = u.adam = 0; memset(u.name, '\0', sizeof u.name); -- 2.11.0