Big ball-of-mud commit
authorMartin Read <mpread@chiark.greenend.org.uk>
Wed, 12 Mar 2014 16:00:50 +0000 (16:00 +0000)
committerMartin Read <mpread@chiark.greenend.org.uk>
Wed, 12 Mar 2014 16:00:50 +0000 (16:00 +0000)
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.

27 files changed:
MANIFEST
Makefile
combat.cc
combo.cc [new file with mode: 0644]
core.hh
deeds.cc
default.permobjs
display-nc.cc
log.cc
main.cc
map.cc
map.hh
mon1.cc
monsters.hh
notify-local-tty.cc
notify.hh
obj1.cc
obumbrata.hh
permobj.hh
permon.hh
player.hh
pmon2.cc
pmon_comp
pobj_comp
role.cc [new file with mode: 0644]
skills.cc [new file with mode: 0644]
u.cc

index c73258c..a532a90 100644 (file)
--- 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
index 7fa3a1c..ae3feac 100644 (file)
--- 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
 
index fe9664c..010b79a 100644 (file)
--- 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 (file)
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 <limits.h>
+#include <string.h>
+#include <stdio.h>
+#include <deque>
+
+/* 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>* 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 (file)
--- a/core.hh
+++ b/core.hh
@@ -38,6 +38,7 @@
 #include <stdbool.h>
 #include <stdio.h>
 #include <arpa/inet.h>
+#include <deque>
 
 #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 */
index 95a166a..309ad0a 100644 (file)
--- 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,
index 8df90c8..5878364 100644 (file)
@@ -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
index 1d9ad51..790b52b 100644 (file)
@@ -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 (file)
--- 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 (file)
--- 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 (file)
--- 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 (file)
--- a/map.hh
+++ b/map.hh
@@ -36,7 +36,7 @@
 #include <deque>
 /* 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 (file)
--- 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;
index d04a98b..7bdeaa3 100644 (file)
@@ -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);
index 4c909f7..2e60718 100644 (file)
@@ -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)
index bbd835f..134bfdb 100644 (file)
--- 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 (file)
--- 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)
     {
index c711a55..f0838b4 100644 (file)
 #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 */
index 20eb221..36bb17b 100644 (file)
@@ -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
index 0e31244..e33fac2 100644 (file)
--- 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
 
index de73524..bcdf25c 100644 (file)
--- a/player.hh
+++ b/player.hh
 
 #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>* 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 */
index 51ecc99..be9bb80 100644 (file)
--- 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
index 0d3576a..51a7f2a 100755 (executable)
--- 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);
index a86e72d..3e89549 100755 (executable)
--- 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 (file)
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 <limits.h>
+#include <string.h>
+#include <stdio.h>
+#include <deque>
+
+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 (file)
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 <limits.h>
+#include <string.h>
+#include <stdio.h>
+#include <deque>
+
+/*! \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 (file)
--- a/u.cc
+++ b/u.cc
@@ -35,7 +35,6 @@
 #include <stdio.h>
 #include <deque>
 
-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<Action> 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);